diff options
Diffstat (limited to 'Utilities/cmcurl/lib')
326 files changed, 155823 insertions, 0 deletions
diff --git a/Utilities/cmcurl/lib/CMakeLists.txt b/Utilities/cmcurl/lib/CMakeLists.txt new file mode 100644 index 0000000..bf25d89 --- /dev/null +++ b/Utilities/cmcurl/lib/CMakeLists.txt @@ -0,0 +1,278 @@ +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### +set(LIB_NAME libcurl) +set(LIBCURL_OUTPUT_NAME libcurl CACHE STRING "Basename of the curl library") +add_definitions(-DBUILDING_LIBCURL) + +configure_file(curl_config.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/curl_config.h) + +transform_makefile_inc("Makefile.inc" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake") +include(${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake) + +list(APPEND HHEADERS + ${CMAKE_CURRENT_BINARY_DIR}/curl_config.h + ) + +# The rest of the build + +include_directories(${CMAKE_CURRENT_BINARY_DIR}/../include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +if(USE_ARES) + include_directories(${CARES_INCLUDE_DIR}) +endif() + +#----------------------------------------------------------------------------- +# CMake-specific curl code. +unset(LIBCURL_OUTPUT_NAME CACHE) + +add_library(cmcurl ${HHEADERS} ${CSOURCES}) +target_compile_definitions(cmcurl INTERFACE CURL_STATICLIB) +target_link_libraries(cmcurl PRIVATE ${CURL_LIBS}) +if(WIN32 AND CMake_BUILD_PCH) + target_precompile_headers(cmcurl PRIVATE "curl_setup.h" "curl_sspi.h" "${CURL_SOURCE_DIR}/include/curl/curl.h") +endif() + +# For windows we want to install OPENSSL_LIBRARIES dlls +# and also copy them into the build tree so that testing +# can find them. +if(CURL_USE_OPENSSL AND OPENSSL_FOUND AND WIN32) + find_file(CMAKE_EAY_DLL NAME libeay32.dll HINTS ${OPENSSL_INCLUDE_DIR}/..) + find_file(CMAKE_SSL_DLL NAME ssleay32.dll HINTS ${OPENSSL_INCLUDE_DIR}/..) + mark_as_advanced(CMAKE_EAY_DLL CMAKE_SSL_DLL) + if(CMAKE_SSL_DLL AND CMAKE_EAY_DLL) + set(CMAKE_CURL_SSL_DLLS ${CMake_BIN_DIR}/${CMAKE_CFG_INTDIR}/libeay32.dll + ${CMake_BIN_DIR}/${CMAKE_CFG_INTDIR}/ssleay32.dll) + add_custom_command(OUTPUT + ${CMake_BIN_DIR}/${CMAKE_CFG_INTDIR}/libeay32.dll + DEPENDS ${CMAKE_EAY_DLL} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_EAY_DLL} + ${CMake_BIN_DIR}/${CMAKE_CFG_INTDIR}/libeay32.dll) + add_custom_command(OUTPUT + ${CMake_BIN_DIR}/${CMAKE_CFG_INTDIR}/ssleay32.dll + DEPENDS ${CMAKE_SSL_DLL} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SSL_DLL} + ${CMake_BIN_DIR}/${CMAKE_CFG_INTDIR}/ssleay32.dll) + install(PROGRAMS ${CMAKE_EAY_DLL} ${CMAKE_SSL_DLL} DESTINATION bin) + endif() +endif() + +return() # The rest of this file is not needed for building within CMake. +#----------------------------------------------------------------------------- + +if(BUILD_TESTING) + add_library( + curlu # special libcurlu library just for unittests + STATIC + EXCLUDE_FROM_ALL + ${HHEADERS} ${CSOURCES} + ) + target_compile_definitions(curlu PUBLIC UNITTESTS CURL_STATICLIB) +endif() + +if(ENABLE_CURLDEBUG) + # We must compile these sources separately to avoid memdebug.h redefinitions + # applying to them. + set_source_files_properties(memdebug.c curl_multibyte.c PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) +endif() + +if(BUILD_TESTING) + target_link_libraries(curlu PRIVATE ${CURL_LIBS}) +endif() + +transform_makefile_inc("Makefile.soname" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake") +include(${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake) + +if(CMAKE_SYSTEM_NAME STREQUAL "AIX" OR + CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR + CMAKE_SYSTEM_NAME STREQUAL "SunOS" OR + CMAKE_SYSTEM_NAME STREQUAL "GNU/kFreeBSD" OR + + # FreeBSD comes with the a.out and elf flavours + # but a.out was supported up to version 3.x and + # elf from 3.x. I cannot imagine someone running + # CMake on those ancient systems + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + + CMAKE_SYSTEM_NAME STREQUAL "Haiku") + + math(EXPR CMAKESONAME "${VERSIONCHANGE} - ${VERSIONDEL}") + set(CMAKEVERSION "${CMAKESONAME}.${VERSIONDEL}.${VERSIONADD}") +else() + unset(CMAKESONAME) +endif() + +## Library definition + +# Add "_imp" as a suffix before the extension to avoid conflicting with +# the statically linked "libcurl.lib" (typically with MSVC) +if(WIN32 AND + NOT IMPORT_LIB_SUFFIX AND + CMAKE_STATIC_LIBRARY_SUFFIX STREQUAL CMAKE_IMPORT_LIBRARY_SUFFIX) + set(IMPORT_LIB_SUFFIX "_imp") +endif() + +# Whether to do a single compilation pass for libcurl sources and reuse these +# objects to generate both static and shared target. +if(NOT DEFINED SHARE_LIB_OBJECT) + # Enable it by default on platforms where PIC is the default for both shared + # and static and there is a way to tell the linker which libcurl symbols it + # should export (vs. marking these symbols exportable at compile-time). + if(WIN32) + set(SHARE_LIB_OBJECT ON) + else() + # On other platforms, make it an option disabled by default + set(SHARE_LIB_OBJECT OFF) + endif() +endif() + +if(WIN32) + # Define CURL_STATICLIB always, to disable __declspec(dllexport) for exported + # libcurl symbols. We handle exports via libcurl.def instead. Except with + # symbol hiding disabled or debug mode enabled, when we export _all_ symbols + # from libcurl DLL, without using libcurl.def. + add_definitions("-DCURL_STATICLIB") +endif() + +if(SHARE_LIB_OBJECT) + set(LIB_OBJECT "libcurl_object") + add_library(${LIB_OBJECT} OBJECT ${HHEADERS} ${CSOURCES}) + target_link_libraries(${LIB_OBJECT} PRIVATE ${CURL_LIBS}) + set_target_properties(${LIB_OBJECT} PROPERTIES + POSITION_INDEPENDENT_CODE ON) + if(HIDES_CURL_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") + endif() + if(CURL_HAS_LTO) + set_target_properties(${LIB_OBJECT} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) + endif() + + target_include_directories(${LIB_OBJECT} INTERFACE + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> + $<BUILD_INTERFACE:${CURL_SOURCE_DIR}/include>) + + set(LIB_SOURCE $<TARGET_OBJECTS:${LIB_OBJECT}>) +else() + set(LIB_SOURCE ${HHEADERS} ${CSOURCES}) +endif() + +# we want it to be called libcurl on all platforms +if(BUILD_STATIC_LIBS) + list(APPEND libcurl_export ${LIB_STATIC}) + add_library(${LIB_STATIC} STATIC ${LIB_SOURCE}) + add_library(${PROJECT_NAME}::${LIB_STATIC} ALIAS ${LIB_STATIC}) + target_link_libraries(${LIB_STATIC} PRIVATE ${CURL_LIBS}) + # Remove the "lib" prefix since the library is already named "libcurl". + set_target_properties(${LIB_STATIC} PROPERTIES + PREFIX "" OUTPUT_NAME "${LIBCURL_OUTPUT_NAME}" + SUFFIX "${STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}" + INTERFACE_COMPILE_DEFINITIONS "CURL_STATICLIB") + if(HIDES_CURL_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") + endif() + if(CURL_HAS_LTO) + set_target_properties(${LIB_STATIC} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) + endif() + if(CMAKEVERSION AND CMAKESONAME) + set_target_properties(${LIB_STATIC} PROPERTIES + VERSION ${CMAKEVERSION} SOVERSION ${CMAKESONAME}) + endif() + + target_include_directories(${LIB_STATIC} INTERFACE + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> + $<BUILD_INTERFACE:${CURL_SOURCE_DIR}/include>) +endif() + +if(BUILD_SHARED_LIBS) + list(APPEND libcurl_export ${LIB_SHARED}) + add_library(${LIB_SHARED} SHARED ${LIB_SOURCE}) + add_library(${PROJECT_NAME}::${LIB_SHARED} ALIAS ${LIB_SHARED}) + if(WIN32) + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY SOURCES libcurl.rc) + if(HIDES_CURL_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY SOURCES "${CURL_SOURCE_DIR}/libcurl.def") + endif() + endif() + target_link_libraries(${LIB_SHARED} PRIVATE ${CURL_LIBS}) + # Remove the "lib" prefix since the library is already named "libcurl". + set_target_properties(${LIB_SHARED} PROPERTIES + PREFIX "" OUTPUT_NAME "${LIBCURL_OUTPUT_NAME}" + IMPORT_PREFIX "" IMPORT_SUFFIX "${IMPORT_LIB_SUFFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX}" + POSITION_INDEPENDENT_CODE ON) + if(HIDES_CURL_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") + endif() + if(CURL_HAS_LTO) + set_target_properties(${LIB_SHARED} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) + endif() + if(CMAKEVERSION AND CMAKESONAME) + set_target_properties(${LIB_SHARED} PROPERTIES + VERSION ${CMAKEVERSION} SOVERSION ${CMAKESONAME}) + endif() + + target_include_directories(${LIB_SHARED} INTERFACE + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> + $<BUILD_INTERFACE:${CURL_SOURCE_DIR}/include>) +endif() + +add_library(${LIB_NAME} ALIAS ${LIB_SELECTED}) +add_library(${PROJECT_NAME}::${LIB_NAME} ALIAS ${LIB_SELECTED}) + +if(CURL_ENABLE_EXPORT_TARGET) + if(BUILD_STATIC_LIBS) + install(TARGETS ${LIB_STATIC} + EXPORT ${TARGETS_EXPORT_NAME} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + endif() + if(BUILD_SHARED_LIBS) + install(TARGETS ${LIB_SHARED} + EXPORT ${TARGETS_EXPORT_NAME} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + endif() + + export(TARGETS ${libcurl_export} + FILE ${PROJECT_BINARY_DIR}/libcurl-target.cmake + NAMESPACE ${PROJECT_NAME}:: + ) +endif() diff --git a/Utilities/cmcurl/lib/Makefile.inc b/Utilities/cmcurl/lib/Makefile.inc new file mode 100644 index 0000000..e568ef9 --- /dev/null +++ b/Utilities/cmcurl/lib/Makefile.inc @@ -0,0 +1,370 @@ +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### + +LIB_VAUTH_CFILES = \ + vauth/cleartext.c \ + vauth/cram.c \ + vauth/digest.c \ + vauth/digest_sspi.c \ + vauth/gsasl.c \ + vauth/krb5_gssapi.c \ + vauth/krb5_sspi.c \ + vauth/ntlm.c \ + vauth/ntlm_sspi.c \ + vauth/oauth2.c \ + vauth/spnego_gssapi.c \ + vauth/spnego_sspi.c \ + vauth/vauth.c + +LIB_VAUTH_HFILES = \ + vauth/digest.h \ + vauth/ntlm.h \ + vauth/vauth.h + +LIB_VTLS_CFILES = \ + vtls/bearssl.c \ + vtls/gtls.c \ + vtls/hostcheck.c \ + vtls/keylog.c \ + vtls/mbedtls.c \ + vtls/mbedtls_threadlock.c \ + vtls/openssl.c \ + vtls/rustls.c \ + vtls/schannel.c \ + vtls/schannel_verify.c \ + vtls/sectransp.c \ + vtls/vtls.c \ + vtls/wolfssl.c \ + vtls/x509asn1.c + +LIB_VTLS_HFILES = \ + vtls/bearssl.h \ + vtls/gtls.h \ + vtls/hostcheck.h \ + vtls/keylog.h \ + vtls/mbedtls.h \ + vtls/mbedtls_threadlock.h \ + vtls/openssl.h \ + vtls/rustls.h \ + vtls/schannel.h \ + vtls/schannel_int.h \ + vtls/sectransp.h \ + vtls/vtls.h \ + vtls/vtls_int.h \ + vtls/wolfssl.h \ + vtls/x509asn1.h + +LIB_VQUIC_CFILES = \ + vquic/curl_msh3.c \ + vquic/curl_ngtcp2.c \ + vquic/curl_quiche.c \ + vquic/vquic.c + +LIB_VQUIC_HFILES = \ + vquic/curl_msh3.h \ + vquic/curl_ngtcp2.h \ + vquic/curl_quiche.h \ + vquic/vquic.h \ + vquic/vquic_int.h + +LIB_VSSH_CFILES = \ + vssh/libssh.c \ + vssh/libssh2.c \ + vssh/wolfssh.c + +LIB_VSSH_HFILES = \ + vssh/ssh.h + +LIB_CFILES = \ + altsvc.c \ + amigaos.c \ + asyn-ares.c \ + asyn-thread.c \ + base64.c \ + bufq.c \ + bufref.c \ + c-hyper.c \ + cf-h1-proxy.c \ + cf-h2-proxy.c \ + cf-haproxy.c \ + cf-https-connect.c \ + cf-socket.c \ + cfilters.c \ + conncache.c \ + connect.c \ + content_encoding.c \ + cookie.c \ + curl_addrinfo.c \ + curl_des.c \ + curl_endian.c \ + curl_fnmatch.c \ + curl_get_line.c \ + curl_gethostname.c \ + curl_gssapi.c \ + curl_memrchr.c \ + curl_multibyte.c \ + curl_ntlm_core.c \ + curl_ntlm_wb.c \ + curl_path.c \ + curl_range.c \ + curl_rtmp.c \ + curl_sasl.c \ + curl_sspi.c \ + curl_threads.c \ + curl_trc.c \ + dict.c \ + doh.c \ + dynbuf.c \ + dynhds.c \ + easy.c \ + easygetopt.c \ + easyoptions.c \ + escape.c \ + file.c \ + fileinfo.c \ + fopen.c \ + formdata.c \ + ftp.c \ + ftplistparser.c \ + getenv.c \ + getinfo.c \ + gopher.c \ + hash.c \ + headers.c \ + hmac.c \ + hostasyn.c \ + hostip.c \ + hostip4.c \ + hostip6.c \ + hostsyn.c \ + hsts.c \ + http.c \ + http1.c \ + http2.c \ + http_aws_sigv4.c \ + http_chunks.c \ + http_digest.c \ + http_negotiate.c \ + http_ntlm.c \ + http_proxy.c \ + idn.c \ + if2ip.c \ + imap.c \ + inet_ntop.c \ + inet_pton.c \ + krb5.c \ + ldap.c \ + llist.c \ + macos.c \ + md4.c \ + md5.c \ + memdebug.c \ + mime.c \ + mprintf.c \ + mqtt.c \ + multi.c \ + netrc.c \ + nonblock.c \ + noproxy.c \ + openldap.c \ + parsedate.c \ + pingpong.c \ + pop3.c \ + progress.c \ + psl.c \ + rand.c \ + rename.c \ + rtsp.c \ + select.c \ + sendf.c \ + setopt.c \ + sha256.c \ + share.c \ + slist.c \ + smb.c \ + smtp.c \ + socketpair.c \ + socks.c \ + socks_gssapi.c \ + socks_sspi.c \ + speedcheck.c \ + splay.c \ + strcase.c \ + strdup.c \ + strerror.c \ + strtok.c \ + strtoofft.c \ + system_win32.c \ + telnet.c \ + tftp.c \ + timediff.c \ + timeval.c \ + transfer.c \ + url.c \ + urlapi.c \ + version.c \ + version_win32.c \ + warnless.c \ + ws.c + +LIB_HFILES = \ + altsvc.h \ + amigaos.h \ + arpa_telnet.h \ + asyn.h \ + bufq.h \ + bufref.h \ + c-hyper.h \ + cf-h1-proxy.h \ + cf-h2-proxy.h \ + cf-haproxy.h \ + cf-https-connect.h \ + cf-socket.h \ + cfilters.h \ + conncache.h \ + connect.h \ + content_encoding.h \ + cookie.h \ + curl_addrinfo.h \ + curl_base64.h \ + curl_ctype.h \ + curl_des.h \ + curl_endian.h \ + curl_fnmatch.h \ + curl_get_line.h \ + curl_gethostname.h \ + curl_gssapi.h \ + curl_hmac.h \ + curl_krb5.h \ + curl_ldap.h \ + curl_md4.h \ + curl_md5.h \ + curl_memory.h \ + curl_memrchr.h \ + curl_multibyte.h \ + curl_ntlm_core.h \ + curl_ntlm_wb.h \ + curl_path.h \ + curl_printf.h \ + curl_range.h \ + curl_rtmp.h \ + curl_sasl.h \ + curl_setup.h \ + curl_setup_once.h \ + curl_sha256.h \ + curl_sspi.h \ + curl_threads.h \ + curl_trc.h \ + curlx.h \ + dict.h \ + doh.h \ + dynbuf.h \ + dynhds.h \ + easy_lock.h \ + easyif.h \ + easyoptions.h \ + escape.h \ + file.h \ + fileinfo.h \ + fopen.h \ + formdata.h \ + ftp.h \ + ftplistparser.h \ + functypes.h \ + getinfo.h \ + gopher.h \ + hash.h \ + headers.h \ + hostip.h \ + hsts.h \ + http.h \ + http1.h \ + http2.h \ + http_aws_sigv4.h \ + http_chunks.h \ + http_digest.h \ + http_negotiate.h \ + http_ntlm.h \ + http_proxy.h \ + idn.h \ + if2ip.h \ + imap.h \ + inet_ntop.h \ + inet_pton.h \ + llist.h \ + macos.h \ + memdebug.h \ + mime.h \ + mqtt.h \ + multihandle.h \ + multiif.h \ + netrc.h \ + nonblock.h \ + noproxy.h \ + parsedate.h \ + pingpong.h \ + pop3.h \ + progress.h \ + psl.h \ + rand.h \ + rename.h \ + rtsp.h \ + select.h \ + sendf.h \ + setopt.h \ + setup-vms.h \ + share.h \ + sigpipe.h \ + slist.h \ + smb.h \ + smtp.h \ + sockaddr.h \ + socketpair.h \ + socks.h \ + speedcheck.h \ + splay.h \ + strcase.h \ + strdup.h \ + strerror.h \ + strtok.h \ + strtoofft.h \ + system_win32.h \ + telnet.h \ + tftp.h \ + timediff.h \ + timeval.h \ + transfer.h \ + url.h \ + urlapi-int.h \ + urldata.h \ + version_win32.h \ + warnless.h \ + ws.h + +LIB_RCFILES = libcurl.rc + +CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VTLS_CFILES) \ + $(LIB_VQUIC_CFILES) $(LIB_VSSH_CFILES) +HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VTLS_HFILES) \ + $(LIB_VQUIC_HFILES) $(LIB_VSSH_HFILES) diff --git a/Utilities/cmcurl/lib/altsvc.c b/Utilities/cmcurl/lib/altsvc.c new file mode 100644 index 0000000..35450d6 --- /dev/null +++ b/Utilities/cmcurl/lib/altsvc.c @@ -0,0 +1,717 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * The Alt-Svc: header is defined in RFC 7838: + * https://datatracker.ietf.org/doc/html/rfc7838 + */ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC) +#include <curl/curl.h> +#include "urldata.h" +#include "altsvc.h" +#include "curl_get_line.h" +#include "strcase.h" +#include "parsedate.h" +#include "sendf.h" +#include "warnless.h" +#include "fopen.h" +#include "rename.h" +#include "strdup.h" +#include "inet_pton.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define MAX_ALTSVC_LINE 4095 +#define MAX_ALTSVC_DATELENSTR "64" +#define MAX_ALTSVC_DATELEN 64 +#define MAX_ALTSVC_HOSTLENSTR "512" +#define MAX_ALTSVC_HOSTLEN 512 +#define MAX_ALTSVC_ALPNLENSTR "10" +#define MAX_ALTSVC_ALPNLEN 10 + +#define H3VERSION "h3" + +static enum alpnid alpn2alpnid(char *name) +{ + if(strcasecompare(name, "h1")) + return ALPN_h1; + if(strcasecompare(name, "h2")) + return ALPN_h2; + if(strcasecompare(name, H3VERSION)) + return ALPN_h3; + return ALPN_none; /* unknown, probably rubbish input */ +} + +/* Given the ALPN ID, return the name */ +const char *Curl_alpnid2str(enum alpnid id) +{ + switch(id) { + case ALPN_h1: + return "h1"; + case ALPN_h2: + return "h2"; + case ALPN_h3: + return H3VERSION; + default: + return ""; /* bad */ + } +} + + +static void altsvc_free(struct altsvc *as) +{ + free(as->src.host); + free(as->dst.host); + free(as); +} + +static struct altsvc *altsvc_createid(const char *srchost, + const char *dsthost, + enum alpnid srcalpnid, + enum alpnid dstalpnid, + unsigned int srcport, + unsigned int dstport) +{ + struct altsvc *as = calloc(1, sizeof(struct altsvc)); + size_t hlen; + size_t dlen; + if(!as) + return NULL; + hlen = strlen(srchost); + dlen = strlen(dsthost); + DEBUGASSERT(hlen); + DEBUGASSERT(dlen); + if(!hlen || !dlen) + /* bad input */ + return NULL; + if((hlen > 2) && srchost[0] == '[') { + /* IPv6 address, strip off brackets */ + srchost++; + hlen -= 2; + } + else if(srchost[hlen - 1] == '.') + /* strip off trailing dot */ + hlen--; + if((dlen > 2) && dsthost[0] == '[') { + /* IPv6 address, strip off brackets */ + dsthost++; + dlen -= 2; + } + + as->src.host = Curl_strndup(srchost, hlen); + if(!as->src.host) + goto error; + + as->dst.host = Curl_strndup(dsthost, dlen); + if(!as->dst.host) + goto error; + + as->src.alpnid = srcalpnid; + as->dst.alpnid = dstalpnid; + as->src.port = curlx_ultous(srcport); + as->dst.port = curlx_ultous(dstport); + + return as; +error: + altsvc_free(as); + return NULL; +} + +static struct altsvc *altsvc_create(char *srchost, + char *dsthost, + char *srcalpn, + char *dstalpn, + unsigned int srcport, + unsigned int dstport) +{ + enum alpnid dstalpnid = alpn2alpnid(dstalpn); + enum alpnid srcalpnid = alpn2alpnid(srcalpn); + if(!srcalpnid || !dstalpnid) + return NULL; + return altsvc_createid(srchost, dsthost, srcalpnid, dstalpnid, + srcport, dstport); +} + +/* only returns SERIOUS errors */ +static CURLcode altsvc_add(struct altsvcinfo *asi, char *line) +{ + /* Example line: + h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1 + */ + char srchost[MAX_ALTSVC_HOSTLEN + 1]; + char dsthost[MAX_ALTSVC_HOSTLEN + 1]; + char srcalpn[MAX_ALTSVC_ALPNLEN + 1]; + char dstalpn[MAX_ALTSVC_ALPNLEN + 1]; + char date[MAX_ALTSVC_DATELEN + 1]; + unsigned int srcport; + unsigned int dstport; + unsigned int prio; + unsigned int persist; + int rc; + + rc = sscanf(line, + "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u " + "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u " + "\"%" MAX_ALTSVC_DATELENSTR "[^\"]\" %u %u", + srcalpn, srchost, &srcport, + dstalpn, dsthost, &dstport, + date, &persist, &prio); + if(9 == rc) { + struct altsvc *as; + time_t expires = Curl_getdate_capped(date); + as = altsvc_create(srchost, dsthost, srcalpn, dstalpn, srcport, dstport); + if(as) { + as->expires = expires; + as->prio = prio; + as->persist = persist ? 1 : 0; + Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node); + } + } + + return CURLE_OK; +} + +/* + * Load alt-svc entries from the given file. The text based line-oriented file + * format is documented here: https://curl.se/docs/alt-svc.html + * + * This function only returns error on major problems that prevent alt-svc + * handling to work completely. It will ignore individual syntactical errors + * etc. + */ +static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) +{ + CURLcode result = CURLE_OK; + char *line = NULL; + FILE *fp; + + /* we need a private copy of the file name so that the altsvc cache file + name survives an easy handle reset */ + free(asi->filename); + asi->filename = strdup(file); + if(!asi->filename) + return CURLE_OUT_OF_MEMORY; + + fp = fopen(file, FOPEN_READTEXT); + if(fp) { + line = malloc(MAX_ALTSVC_LINE); + if(!line) + goto fail; + while(Curl_get_line(line, MAX_ALTSVC_LINE, fp)) { + char *lineptr = line; + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; + if(*lineptr == '#') + /* skip commented lines */ + continue; + + altsvc_add(asi, lineptr); + } + free(line); /* free the line buffer */ + fclose(fp); + } + return result; + +fail: + Curl_safefree(asi->filename); + free(line); + fclose(fp); + return CURLE_OUT_OF_MEMORY; +} + +/* + * Write this single altsvc entry to a single output line + */ + +static CURLcode altsvc_out(struct altsvc *as, FILE *fp) +{ + struct tm stamp; + const char *dst6_pre = ""; + const char *dst6_post = ""; + const char *src6_pre = ""; + const char *src6_post = ""; + CURLcode result = Curl_gmtime(as->expires, &stamp); + if(result) + return result; +#ifdef ENABLE_IPV6 + else { + char ipv6_unused[16]; + if(1 == Curl_inet_pton(AF_INET6, as->dst.host, ipv6_unused)) { + dst6_pre = "["; + dst6_post = "]"; + } + if(1 == Curl_inet_pton(AF_INET6, as->src.host, ipv6_unused)) { + src6_pre = "["; + src6_post = "]"; + } + } +#endif + fprintf(fp, + "%s %s%s%s %u " + "%s %s%s%s %u " + "\"%d%02d%02d " + "%02d:%02d:%02d\" " + "%u %d\n", + Curl_alpnid2str(as->src.alpnid), + src6_pre, as->src.host, src6_post, + as->src.port, + + Curl_alpnid2str(as->dst.alpnid), + dst6_pre, as->dst.host, dst6_post, + as->dst.port, + + stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday, + stamp.tm_hour, stamp.tm_min, stamp.tm_sec, + as->persist, as->prio); + return CURLE_OK; +} + +/* ---- library-wide functions below ---- */ + +/* + * Curl_altsvc_init() creates a new altsvc cache. + * It returns the new instance or NULL if something goes wrong. + */ +struct altsvcinfo *Curl_altsvc_init(void) +{ + struct altsvcinfo *asi = calloc(1, sizeof(struct altsvcinfo)); + if(!asi) + return NULL; + Curl_llist_init(&asi->list, NULL); + + /* set default behavior */ + asi->flags = CURLALTSVC_H1 +#ifdef USE_HTTP2 + | CURLALTSVC_H2 +#endif +#ifdef ENABLE_QUIC + | CURLALTSVC_H3 +#endif + ; + return asi; +} + +/* + * Curl_altsvc_load() loads alt-svc from file. + */ +CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file) +{ + CURLcode result; + DEBUGASSERT(asi); + result = altsvc_load(asi, file); + return result; +} + +/* + * Curl_altsvc_ctrl() passes on the external bitmask. + */ +CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl) +{ + DEBUGASSERT(asi); + if(!ctrl) + /* unexpected */ + return CURLE_BAD_FUNCTION_ARGUMENT; + asi->flags = ctrl; + return CURLE_OK; +} + +/* + * Curl_altsvc_cleanup() frees an altsvc cache instance and all associated + * resources. + */ +void Curl_altsvc_cleanup(struct altsvcinfo **altsvcp) +{ + struct Curl_llist_element *e; + struct Curl_llist_element *n; + if(*altsvcp) { + struct altsvcinfo *altsvc = *altsvcp; + for(e = altsvc->list.head; e; e = n) { + struct altsvc *as = e->ptr; + n = e->next; + altsvc_free(as); + } + free(altsvc->filename); + free(altsvc); + *altsvcp = NULL; /* clear the pointer */ + } +} + +/* + * Curl_altsvc_save() writes the altsvc cache to a file. + */ +CURLcode Curl_altsvc_save(struct Curl_easy *data, + struct altsvcinfo *altsvc, const char *file) +{ + struct Curl_llist_element *e; + struct Curl_llist_element *n; + CURLcode result = CURLE_OK; + FILE *out; + char *tempstore = NULL; + + if(!altsvc) + /* no cache activated */ + return CURLE_OK; + + /* if not new name is given, use the one we stored from the load */ + if(!file && altsvc->filename) + file = altsvc->filename; + + if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file || !file[0]) + /* marked as read-only, no file or zero length file name */ + return CURLE_OK; + + result = Curl_fopen(data, file, &out, &tempstore); + if(!result) { + fputs("# Your alt-svc cache. https://curl.se/docs/alt-svc.html\n" + "# This file was generated by libcurl! Edit at your own risk.\n", + out); + for(e = altsvc->list.head; e; e = n) { + struct altsvc *as = e->ptr; + n = e->next; + result = altsvc_out(as, out); + if(result) + break; + } + fclose(out); + if(!result && tempstore && Curl_rename(tempstore, file)) + result = CURLE_WRITE_ERROR; + + if(result && tempstore) + unlink(tempstore); + } + free(tempstore); + return result; +} + +static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen) +{ + size_t len; + const char *protop; + const char *p = *ptr; + while(*p && ISBLANK(*p)) + p++; + protop = p; + while(*p && !ISBLANK(*p) && (*p != ';') && (*p != '=')) + p++; + len = p - protop; + *ptr = p; + + if(!len || (len >= buflen)) + return CURLE_BAD_FUNCTION_ARGUMENT; + memcpy(alpnbuf, protop, len); + alpnbuf[len] = 0; + return CURLE_OK; +} + +/* hostcompare() returns true if 'host' matches 'check'. The first host + * argument may have a trailing dot present that will be ignored. + */ +static bool hostcompare(const char *host, const char *check) +{ + size_t hlen = strlen(host); + size_t clen = strlen(check); + + if(hlen && (host[hlen - 1] == '.')) + hlen--; + if(hlen != clen) + /* they can't match if they have different lengths */ + return FALSE; + return strncasecompare(host, check, hlen); +} + +/* 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) +{ + struct Curl_llist_element *e; + struct Curl_llist_element *n; + for(e = asi->list.head; e; e = n) { + struct altsvc *as = e->ptr; + n = e->next; + if((srcalpnid == as->src.alpnid) && + (srcport == as->src.port) && + hostcompare(srchost, as->src.host)) { + Curl_llist_remove(&asi->list, e, NULL); + altsvc_free(as); + } + } +} + +#ifdef DEBUGBUILD +/* to play well with debug builds, we can *set* a fixed time this will + return */ +static time_t altsvc_debugtime(void *unused) +{ + char *timestr = getenv("CURL_TIME"); + (void)unused; + if(timestr) { + unsigned long val = strtol(timestr, NULL, 10); + return (time_t)val; + } + return time(NULL); +} +#undef time +#define time(x) altsvc_debugtime(x) +#endif + +#define ISNEWLINE(x) (((x) == '\n') || (x) == '\r') + +/* + * Curl_altsvc_parse() takes an incoming alt-svc response header and stores + * the data correctly in the cache. + * + * 'value' points to the header *value*. That's contents to the right of the + * header name. + * + * Currently this function rejects invalid data without returning an error. + * Invalid host name, port number will result in the specific alternative + * being rejected. Unknown protocols are skipped. + */ +CURLcode Curl_altsvc_parse(struct Curl_easy *data, + struct altsvcinfo *asi, const char *value, + enum alpnid srcalpnid, const char *srchost, + unsigned short srcport) +{ + const char *p = value; + size_t len; + char namebuf[MAX_ALTSVC_HOSTLEN] = ""; + char alpnbuf[MAX_ALTSVC_ALPNLEN] = ""; + struct altsvc *as; + unsigned short dstport = srcport; /* the same by default */ + CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf)); + size_t entries = 0; +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)data; +#endif + if(result) { + infof(data, "Excessive alt-svc header, ignoring."); + return CURLE_OK; + } + + DEBUGASSERT(asi); + + /* "clear" is a magic keyword */ + if(strcasecompare(alpnbuf, "clear")) { + /* Flush cached alternatives for this source origin */ + altsvc_flush(asi, srcalpnid, srchost, srcport); + return CURLE_OK; + } + + do { + if(*p == '=') { + /* [protocol]="[host][:port]" */ + enum alpnid dstalpnid = alpn2alpnid(alpnbuf); /* the same by default */ + p++; + if(*p == '\"') { + const char *dsthost = ""; + const char *value_ptr; + char option[32]; + unsigned long num; + char *end_ptr; + bool quoted = FALSE; + time_t maxage = 24 * 3600; /* default is 24 hours */ + bool persist = FALSE; + bool valid = TRUE; + p++; + if(*p != ':') { + /* host name starts here */ + const char *hostp = p; + if(*p == '[') { + /* pass all valid IPv6 letters - does not handle zone id */ + len = strspn(++p, "0123456789abcdefABCDEF:."); + if(p[len] != ']') + /* invalid host syntax, bail out */ + break; + /* we store the IPv6 numerical address *with* brackets */ + len += 2; + p = &p[len-1]; + } + else { + while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-'))) + p++; + len = p - hostp; + } + if(!len || (len >= MAX_ALTSVC_HOSTLEN)) { + infof(data, "Excessive alt-svc host name, ignoring."); + valid = FALSE; + } + else { + memcpy(namebuf, hostp, len); + namebuf[len] = 0; + dsthost = namebuf; + } + } + else { + /* no destination name, use source host */ + dsthost = srchost; + } + if(*p == ':') { + unsigned long port = 0; + p++; + if(ISDIGIT(*p)) + /* a port number */ + port = strtoul(p, &end_ptr, 10); + else + end_ptr = (char *)p; /* not left uninitialized */ + if(!port || port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') { + infof(data, "Unknown alt-svc port number, ignoring."); + valid = FALSE; + } + else { + dstport = curlx_ultous(port); + p = end_ptr; + } + } + if(*p++ != '\"') + break; + /* Handle the optional 'ma' and 'persist' flags. Unknown flags + are skipped. */ + for(;;) { + while(ISBLANK(*p)) + p++; + if(*p != ';') + break; + p++; /* pass the semicolon */ + if(!*p || ISNEWLINE(*p)) + break; + result = getalnum(&p, option, sizeof(option)); + if(result) { + /* skip option if name is too long */ + option[0] = '\0'; + } + while(*p && ISBLANK(*p)) + p++; + if(*p != '=') + return CURLE_OK; + p++; + while(*p && ISBLANK(*p)) + p++; + if(!*p) + return CURLE_OK; + if(*p == '\"') { + /* quoted value */ + p++; + quoted = TRUE; + } + value_ptr = p; + if(quoted) { + while(*p && *p != '\"') + p++; + if(!*p++) + return CURLE_OK; + } + else { + while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',') + p++; + } + num = strtoul(value_ptr, &end_ptr, 10); + if((end_ptr != value_ptr) && (num < ULONG_MAX)) { + if(strcasecompare("ma", option)) + maxage = num; + else if(strcasecompare("persist", option) && (num == 1)) + persist = TRUE; + } + } + if(dstalpnid && valid) { + 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, dsthost, + srcalpnid, dstalpnid, + srcport, dstport); + if(as) { + /* The expires time also needs to take the Age: value (if any) into + account. [See RFC 7838 section 3.1] */ + as->expires = maxage + time(NULL); + as->persist = persist; + Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node); + infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport, + Curl_alpnid2str(dstalpnid)); + } + } + } + else + break; + /* after the double quote there can be a comma if there's another + string or a semicolon if no more */ + if(*p == ',') { + /* comma means another alternative is presented */ + p++; + result = getalnum(&p, alpnbuf, sizeof(alpnbuf)); + if(result) + break; + } + } + else + break; + } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r')); + + return CURLE_OK; +} + +/* + * Return TRUE on a match + */ +bool Curl_altsvc_lookup(struct altsvcinfo *asi, + enum alpnid srcalpnid, const char *srchost, + int srcport, + struct altsvc **dstentry, + const int versions) /* one or more bits */ +{ + struct Curl_llist_element *e; + struct Curl_llist_element *n; + time_t now = time(NULL); + DEBUGASSERT(asi); + DEBUGASSERT(srchost); + DEBUGASSERT(dstentry); + + for(e = asi->list.head; e; e = n) { + struct altsvc *as = e->ptr; + n = e->next; + if(as->expires < now) { + /* an expired entry, remove */ + Curl_llist_remove(&asi->list, e, NULL); + altsvc_free(as); + continue; + } + if((as->src.alpnid == srcalpnid) && + hostcompare(srchost, as->src.host) && + (as->src.port == srcport) && + (versions & as->dst.alpnid)) { + /* match */ + *dstentry = as; + return TRUE; + } + } + return FALSE; +} + +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_ALTSVC */ diff --git a/Utilities/cmcurl/lib/altsvc.h b/Utilities/cmcurl/lib/altsvc.h new file mode 100644 index 0000000..7fea143 --- /dev/null +++ b/Utilities/cmcurl/lib/altsvc.h @@ -0,0 +1,81 @@ +#ifndef HEADER_CURL_ALTSVC_H +#define HEADER_CURL_ALTSVC_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC) +#include <curl/curl.h> +#include "llist.h" + +enum alpnid { + ALPN_none = 0, + ALPN_h1 = CURLALTSVC_H1, + ALPN_h2 = CURLALTSVC_H2, + ALPN_h3 = CURLALTSVC_H3 +}; + +struct althost { + char *host; + unsigned short port; + enum alpnid alpnid; +}; + +struct altsvc { + struct althost src; + struct althost dst; + time_t expires; + bool persist; + int prio; + struct Curl_llist_element node; +}; + +struct altsvcinfo { + char *filename; + struct Curl_llist list; /* list of entries */ + long flags; /* the publicly set bitmask */ +}; + +const char *Curl_alpnid2str(enum alpnid id); +struct altsvcinfo *Curl_altsvc_init(void); +CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file); +CURLcode Curl_altsvc_save(struct Curl_easy *data, + struct altsvcinfo *asi, const char *file); +CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl); +void Curl_altsvc_cleanup(struct altsvcinfo **altsvc); +CURLcode Curl_altsvc_parse(struct Curl_easy *data, + struct altsvcinfo *altsvc, const char *value, + enum alpnid srcalpn, const char *srchost, + unsigned short srcport); +bool Curl_altsvc_lookup(struct altsvcinfo *asi, + enum alpnid srcalpnid, const char *srchost, + int srcport, + struct altsvc **dstentry, + const int versions); /* CURLALTSVC_H* bits */ +#else +/* disabled */ +#define Curl_altsvc_save(a,b,c) +#define Curl_altsvc_cleanup(x) +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_ALTSVC */ +#endif /* HEADER_CURL_ALTSVC_H */ diff --git a/Utilities/cmcurl/lib/amigaos.c b/Utilities/cmcurl/lib/amigaos.c new file mode 100644 index 0000000..139309b --- /dev/null +++ b/Utilities/cmcurl/lib/amigaos.c @@ -0,0 +1,247 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef __AMIGA__ + +#include <curl/curl.h> + +#include "hostip.h" +#include "amigaos.h" + +#ifdef HAVE_PROTO_BSDSOCKET_H +# if defined(__amigaos4__) +# include <bsdsocket/socketbasetags.h> +# elif !defined(USE_AMISSL) +# include <amitcp/socketbasetags.h> +# endif +# ifdef __libnix__ +# include <stabs.h> +# endif +#endif + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef HAVE_PROTO_BSDSOCKET_H + +#ifdef __amigaos4__ +/* + * AmigaOS 4.x specific code + */ + +/* + * hostip4.c - Curl_ipv4_resolve_r() replacement code + * + * Logic that needs to be considered are the following build cases: + * - newlib networking + * - clib2 networking + * - direct bsdsocket.library networking (usually AmiSSL builds) + * Each with the threaded resolver enabled or not. + * + * With the threaded resolver enabled, try to use gethostbyname_r() where + * available, otherwise (re)open bsdsocket.library and fallback to + * gethostbyname(). + */ + +#include <proto/bsdsocket.h> + +static struct SocketIFace *__CurlISocket = NULL; +static uint32 SocketFeatures = 0; + +#define HAVE_BSDSOCKET_GETHOSTBYNAME_R 0x01 +#define HAVE_BSDSOCKET_GETADDRINFO 0x02 + +CURLcode Curl_amiga_init(void) +{ + struct SocketIFace *ISocket; + struct Library *base = OpenLibrary("bsdsocket.library", 4); + + if(base) { + ISocket = (struct SocketIFace *)GetInterface(base, "main", 1, NULL); + if(ISocket) { + ULONG enabled = 0; + + SocketBaseTags(SBTM_SETVAL(SBTC_CAN_SHARE_LIBRARY_BASES), TRUE, + SBTM_GETREF(SBTC_HAVE_GETHOSTADDR_R_API), (ULONG)&enabled, + TAG_DONE); + + if(enabled) { + SocketFeatures |= HAVE_BSDSOCKET_GETHOSTBYNAME_R; + } + + __CurlISocket = ISocket; + + atexit(Curl_amiga_cleanup); + + return CURLE_OK; + } + CloseLibrary(base); + } + + return CURLE_FAILED_INIT; +} + +void Curl_amiga_cleanup(void) +{ + if(__CurlISocket) { + struct Library *base = __CurlISocket->Data.LibBase; + DropInterface((struct Interface *)__CurlISocket); + CloseLibrary(base); + __CurlISocket = NULL; + } +} + +#ifdef CURLRES_AMIGA +/* + * Because we need to handle the different cases in hostip4.c at run-time, + * not at compile-time, based on what was detected in Curl_amiga_init(), + * we replace it completely with our own as to not complicate the baseline + * code. Assumes malloc/calloc/free are thread safe because Curl_he2ai() + * allocates memory also. + */ + +struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, + int port) +{ + struct Curl_addrinfo *ai = NULL; + struct hostent *h; + struct SocketIFace *ISocket = __CurlISocket; + + if(SocketFeatures & HAVE_BSDSOCKET_GETHOSTBYNAME_R) { + LONG h_errnop = 0; + struct hostent *buf; + + buf = calloc(1, CURL_HOSTENT_SIZE); + if(buf) { + h = gethostbyname_r((STRPTR)hostname, buf, + (char *)buf + sizeof(struct hostent), + CURL_HOSTENT_SIZE - sizeof(struct hostent), + &h_errnop); + if(h) { + ai = Curl_he2ai(h, port); + } + free(buf); + } + } + else { + #ifdef CURLRES_THREADED + /* gethostbyname() is not thread safe, so we need to reopen bsdsocket + * on the thread's context + */ + struct Library *base = OpenLibrary("bsdsocket.library", 4); + if(base) { + ISocket = (struct SocketIFace *)GetInterface(base, "main", 1, NULL); + if(ISocket) { + h = gethostbyname((STRPTR)hostname); + if(h) { + ai = Curl_he2ai(h, port); + } + DropInterface((struct Interface *)ISocket); + } + CloseLibrary(base); + } + #else + /* not using threaded resolver - safe to use this as-is */ + h = gethostbyname(hostname); + if(h) { + ai = Curl_he2ai(h, port); + } + #endif + } + + return ai; +} +#endif /* CURLRES_AMIGA */ + +#ifdef USE_AMISSL +#include <signal.h> +int Curl_amiga_select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *errorfds, struct timeval *timeout) +{ + int r = WaitSelect(nfds, readfds, writefds, errorfds, timeout, 0); + /* Ensure Ctrl-C signal is actioned */ + if((r == -1) && (SOCKERRNO == EINTR)) + raise(SIGINT); + return r; +} +#endif /* USE_AMISSL */ + +#elif !defined(USE_AMISSL) /* __amigaos4__ */ +/* + * Amiga OS3 specific code + */ + +struct Library *SocketBase = NULL; +extern int errno, h_errno; + +#ifdef __libnix__ +void __request(const char *msg); +#else +# define __request(msg) Printf(msg "\n\a") +#endif + +void Curl_amiga_cleanup(void) +{ + if(SocketBase) { + CloseLibrary(SocketBase); + SocketBase = NULL; + } +} + +CURLcode Curl_amiga_init(void) +{ + if(!SocketBase) + SocketBase = OpenLibrary("bsdsocket.library", 4); + + if(!SocketBase) { + __request("No TCP/IP Stack running!"); + return CURLE_FAILED_INIT; + } + + if(SocketBaseTags(SBTM_SETVAL(SBTC_ERRNOPTR(sizeof(errno))), (ULONG) &errno, + SBTM_SETVAL(SBTC_LOGTAGPTR), (ULONG) "curl", + TAG_DONE)) { + __request("SocketBaseTags ERROR"); + return CURLE_FAILED_INIT; + } + +#ifndef __libnix__ + atexit(Curl_amiga_cleanup); +#endif + + return CURLE_OK; +} + +#ifdef __libnix__ +ADD2EXIT(Curl_amiga_cleanup, -50); +#endif + +#endif /* !USE_AMISSL */ + +#endif /* HAVE_PROTO_BSDSOCKET_H */ + +#endif /* __AMIGA__ */ diff --git a/Utilities/cmcurl/lib/amigaos.h b/Utilities/cmcurl/lib/amigaos.h new file mode 100644 index 0000000..c99d963 --- /dev/null +++ b/Utilities/cmcurl/lib/amigaos.h @@ -0,0 +1,41 @@ +#ifndef HEADER_CURL_AMIGAOS_H +#define HEADER_CURL_AMIGAOS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if defined(__AMIGA__) && defined(HAVE_PROTO_BSDSOCKET_H) && \ + (!defined(USE_AMISSL) || defined(__amigaos4__)) + +CURLcode Curl_amiga_init(void); +void Curl_amiga_cleanup(void); + +#else + +#define Curl_amiga_init() CURLE_OK +#define Curl_amiga_cleanup() Curl_nop_stmt + +#endif + +#endif /* HEADER_CURL_AMIGAOS_H */ diff --git a/Utilities/cmcurl/lib/arpa_telnet.h b/Utilities/cmcurl/lib/arpa_telnet.h new file mode 100644 index 0000000..228b446 --- /dev/null +++ b/Utilities/cmcurl/lib/arpa_telnet.h @@ -0,0 +1,117 @@ +#ifndef HEADER_CURL_ARPA_TELNET_H +#define HEADER_CURL_ARPA_TELNET_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#ifndef CURL_DISABLE_TELNET +/* + * Telnet option defines. Add more here if in need. + */ +#define CURL_TELOPT_BINARY 0 /* binary 8bit data */ +#define CURL_TELOPT_ECHO 1 /* just echo! */ +#define CURL_TELOPT_SGA 3 /* Suppress Go Ahead */ +#define CURL_TELOPT_EXOPL 255 /* EXtended OPtions List */ +#define CURL_TELOPT_TTYPE 24 /* Terminal TYPE */ +#define CURL_TELOPT_NAWS 31 /* Negotiate About Window Size */ +#define CURL_TELOPT_XDISPLOC 35 /* X DISPlay LOCation */ + +#define CURL_TELOPT_NEW_ENVIRON 39 /* NEW ENVIRONment variables */ +#define CURL_NEW_ENV_VAR 0 +#define CURL_NEW_ENV_VALUE 1 + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +/* + * The telnet options represented as strings + */ +static const char * const telnetoptions[]= +{ + "BINARY", "ECHO", "RCP", "SUPPRESS GO AHEAD", + "NAME", "STATUS", "TIMING MARK", "RCTE", + "NAOL", "NAOP", "NAOCRD", "NAOHTS", + "NAOHTD", "NAOFFD", "NAOVTS", "NAOVTD", + "NAOLFD", "EXTEND ASCII", "LOGOUT", "BYTE MACRO", + "DE TERMINAL", "SUPDUP", "SUPDUP OUTPUT", "SEND LOCATION", + "TERM TYPE", "END OF RECORD", "TACACS UID", "OUTPUT MARKING", + "TTYLOC", "3270 REGIME", "X3 PAD", "NAWS", + "TERM SPEED", "LFLOW", "LINEMODE", "XDISPLOC", + "OLD-ENVIRON", "AUTHENTICATION", "ENCRYPT", "NEW-ENVIRON" +}; +#define CURL_TELOPT(x) telnetoptions[x] +#else +#define CURL_TELOPT(x) "" +#endif + +#define CURL_TELOPT_MAXIMUM CURL_TELOPT_NEW_ENVIRON + +#define CURL_TELOPT_OK(x) ((x) <= CURL_TELOPT_MAXIMUM) + +#define CURL_NTELOPTS 40 + +/* + * First some defines + */ +#define CURL_xEOF 236 /* End Of File */ +#define CURL_SE 240 /* Sub negotiation End */ +#define CURL_NOP 241 /* No OPeration */ +#define CURL_DM 242 /* Data Mark */ +#define CURL_GA 249 /* Go Ahead, reverse the line */ +#define CURL_SB 250 /* SuBnegotiation */ +#define CURL_WILL 251 /* Our side WILL use this option */ +#define CURL_WONT 252 /* Our side WON'T use this option */ +#define CURL_DO 253 /* DO use this option! */ +#define CURL_DONT 254 /* DON'T use this option! */ +#define CURL_IAC 255 /* Interpret As Command */ + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +/* + * Then those numbers represented as strings: + */ +static const char * const telnetcmds[]= +{ + "EOF", "SUSP", "ABORT", "EOR", "SE", + "NOP", "DMARK", "BRK", "IP", "AO", + "AYT", "EC", "EL", "GA", "SB", + "WILL", "WONT", "DO", "DONT", "IAC" +}; +#endif + +#define CURL_TELCMD_MINIMUM CURL_xEOF /* the first one */ +#define CURL_TELCMD_MAXIMUM CURL_IAC /* surprise, 255 is the last one! ;-) */ + +#define CURL_TELQUAL_IS 0 +#define CURL_TELQUAL_SEND 1 +#define CURL_TELQUAL_INFO 2 +#define CURL_TELQUAL_NAME 3 + +#define CURL_TELCMD_OK(x) ( ((unsigned int)(x) >= CURL_TELCMD_MINIMUM) && \ + ((unsigned int)(x) <= CURL_TELCMD_MAXIMUM) ) + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +#define CURL_TELCMD(x) telnetcmds[(x)-CURL_TELCMD_MINIMUM] +#else +#define CURL_TELCMD(x) "" +#endif + +#endif /* CURL_DISABLE_TELNET */ + +#endif /* HEADER_CURL_ARPA_TELNET_H */ diff --git a/Utilities/cmcurl/lib/asyn-ares.c b/Utilities/cmcurl/lib/asyn-ares.c new file mode 100644 index 0000000..437c933 --- /dev/null +++ b/Utilities/cmcurl/lib/asyn-ares.c @@ -0,0 +1,968 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +/*********************************************************************** + * Only for ares-enabled builds + * And only for functions that fulfill the asynch resolver backend API + * as defined in asyn.h, nothing else belongs in this file! + **********************************************************************/ + +#ifdef CURLRES_ARES + +#include <limits.h> +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "url.h" +#include "multiif.h" +#include "inet_pton.h" +#include "connect.h" +#include "select.h" +#include "progress.h" +#include "timediff.h" + +#if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ + defined(_WIN32) +# define CARES_STATICLIB +#endif +#include <ares.h> +#include <ares_version.h> /* really old c-ares didn't include this by + itself */ + +#if ARES_VERSION >= 0x010500 +/* c-ares 1.5.0 or later, the callback proto is modified */ +#define HAVE_CARES_CALLBACK_TIMEOUTS 1 +#endif + +#if ARES_VERSION >= 0x010601 +/* IPv6 supported since 1.6.1 */ +#define HAVE_CARES_IPV6 1 +#endif + +#if ARES_VERSION >= 0x010704 +#define HAVE_CARES_SERVERS_CSV 1 +#define HAVE_CARES_LOCAL_DEV 1 +#define HAVE_CARES_SET_LOCAL 1 +#endif + +#if ARES_VERSION >= 0x010b00 +#define HAVE_CARES_PORTS_CSV 1 +#endif + +#if ARES_VERSION >= 0x011000 +/* 1.16.0 or later has ares_getaddrinfo */ +#define HAVE_CARES_GETADDRINFO 1 +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +struct thread_data { + int num_pending; /* number of outstanding c-ares requests */ + struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares + parts */ + int last_status; +#ifndef HAVE_CARES_GETADDRINFO + struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ +#endif + char hostname[1]; +}; + +/* How long we are willing to wait for additional parallel responses after + obtaining a "definitive" one. For old c-ares without getaddrinfo. + + This is intended to equal the c-ares default timeout. cURL always uses that + default value. Unfortunately, c-ares doesn't expose its default timeout in + its API, but it is officially documented as 5 seconds. + + See query_completed_cb() for an explanation of how this is used. + */ +#define HAPPY_EYEBALLS_DNS_TIMEOUT 5000 + +#define CARES_TIMEOUT_PER_ATTEMPT 2000 + +/* + * Curl_resolver_global_init() - the generic low-level asynchronous name + * resolve API. Called from curl_global_init() to initialize global resolver + * environment. Initializes ares library. + */ +int Curl_resolver_global_init(void) +{ +#ifdef CARES_HAVE_ARES_LIBRARY_INIT + if(ares_library_init(ARES_LIB_INIT_ALL)) { + return CURLE_FAILED_INIT; + } +#endif + return CURLE_OK; +} + +/* + * Curl_resolver_global_cleanup() + * + * Called from curl_global_cleanup() to destroy global resolver environment. + * Deinitializes ares library. + */ +void Curl_resolver_global_cleanup(void) +{ +#ifdef CARES_HAVE_ARES_LIBRARY_CLEANUP + ares_library_cleanup(); +#endif +} + + +static void sock_state_cb(void *data, ares_socket_t socket_fd, + int readable, int writable) +{ + struct Curl_easy *easy = data; + if(!readable && !writable) { + DEBUGASSERT(easy); + Curl_multi_closed(easy, socket_fd); + } +} + +/* + * Curl_resolver_init() + * + * Called from curl_easy_init() -> Curl_open() to initialize resolver + * URL-state specific environment ('resolver' member of the UrlState + * structure). Fills the passed pointer by the initialized ares_channel. + */ +CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) +{ + int status; + struct ares_options options; + int optmask = ARES_OPT_SOCK_STATE_CB; + options.sock_state_cb = sock_state_cb; + options.sock_state_cb_data = easy; + options.timeout = CARES_TIMEOUT_PER_ATTEMPT; + optmask |= ARES_OPT_TIMEOUTMS; + + status = ares_init_options((ares_channel*)resolver, &options, optmask); + if(status != ARES_SUCCESS) { + if(status == ARES_ENOMEM) + return CURLE_OUT_OF_MEMORY; + else + return CURLE_FAILED_INIT; + } + return CURLE_OK; + /* make sure that all other returns from this function should destroy the + ares channel before returning error! */ +} + +/* + * Curl_resolver_cleanup() + * + * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver + * URL-state specific environment ('resolver' member of the UrlState + * structure). Destroys the ares channel. + */ +void Curl_resolver_cleanup(void *resolver) +{ + ares_destroy((ares_channel)resolver); +} + +/* + * Curl_resolver_duphandle() + * + * Called from curl_easy_duphandle() to duplicate resolver URL-state specific + * environment ('resolver' member of the UrlState structure). Duplicates the + * 'from' ares channel and passes the resulting channel to the 'to' pointer. + */ +CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from) +{ + (void)from; + /* + * it would be better to call ares_dup instead, but right now + * it is not possible to set 'sock_state_cb_data' outside of + * ares_init_options + */ + return Curl_resolver_init(easy, to); +} + +static void destroy_async_data(struct Curl_async *async); + +/* + * Cancel all possibly still on-going resolves for this connection. + */ +void Curl_resolver_cancel(struct Curl_easy *data) +{ + DEBUGASSERT(data); + if(data->conn->resolve_async.resolver) + ares_cancel((ares_channel)data->conn->resolve_async.resolver); + destroy_async_data(&data->conn->resolve_async); +} + +/* + * We're equivalent to Curl_resolver_cancel() for the c-ares resolver. We + * never block. + */ +void Curl_resolver_kill(struct Curl_easy *data) +{ + /* We don't need to check the resolver state because we can be called safely + at any time and we always do the same thing. */ + Curl_resolver_cancel(data); +} + +/* + * destroy_async_data() cleans up async resolver data. + */ +static void destroy_async_data(struct Curl_async *async) +{ + if(async->tdata) { + struct thread_data *res = async->tdata; + if(res) { + if(res->temp_ai) { + Curl_freeaddrinfo(res->temp_ai); + res->temp_ai = NULL; + } + free(res); + } + async->tdata = NULL; + } +} + +/* + * Curl_resolver_getsock() is called when someone from the outside world + * (using curl_multi_fdset()) wants to get our fd_set setup and we're talking + * with ares. The caller must make sure that this function is only called when + * we have a working ares channel. + * + * Returns: sockets-in-use-bitmap + */ + +int Curl_resolver_getsock(struct Curl_easy *data, + curl_socket_t *socks) +{ + struct timeval maxtime; + struct timeval timebuf; + struct timeval *timeout; + long milli; + int max = ares_getsock((ares_channel)data->conn->resolve_async.resolver, + (ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE); + + maxtime.tv_sec = CURL_TIMEOUT_RESOLVE; + maxtime.tv_usec = 0; + + timeout = ares_timeout((ares_channel)data->conn->resolve_async.resolver, + &maxtime, &timebuf); + milli = (long)curlx_tvtoms(timeout); + if(milli == 0) + milli += 10; + Curl_expire(data, milli, EXPIRE_ASYNC_NAME); + + return max; +} + +/* + * waitperform() + * + * 1) Ask ares what sockets it currently plays with, then + * 2) wait for the timeout period to check for action on ares' sockets. + * 3) tell ares to act on all the sockets marked as "with action" + * + * return number of sockets it worked on, or -1 on error + */ + +static int waitperform(struct Curl_easy *data, timediff_t timeout_ms) +{ + int nfds; + int bitmask; + ares_socket_t socks[ARES_GETSOCK_MAXNUM]; + struct pollfd pfd[ARES_GETSOCK_MAXNUM]; + int i; + int num = 0; + + bitmask = ares_getsock((ares_channel)data->conn->resolve_async.resolver, + socks, ARES_GETSOCK_MAXNUM); + + for(i = 0; i < ARES_GETSOCK_MAXNUM; i++) { + pfd[i].events = 0; + pfd[i].revents = 0; + if(ARES_GETSOCK_READABLE(bitmask, i)) { + pfd[i].fd = socks[i]; + pfd[i].events |= POLLRDNORM|POLLIN; + } + if(ARES_GETSOCK_WRITABLE(bitmask, i)) { + pfd[i].fd = socks[i]; + pfd[i].events |= POLLWRNORM|POLLOUT; + } + if(pfd[i].events) + num++; + else + break; + } + + if(num) { + nfds = Curl_poll(pfd, num, timeout_ms); + if(nfds < 0) + return -1; + } + else + nfds = 0; + + if(!nfds) + /* Call ares_process() unconditionally here, even if we simply timed out + above, as otherwise the ares name resolve won't timeout! */ + ares_process_fd((ares_channel)data->conn->resolve_async.resolver, + ARES_SOCKET_BAD, ARES_SOCKET_BAD); + else { + /* move through the descriptors and ask for processing on them */ + for(i = 0; i < num; i++) + ares_process_fd((ares_channel)data->conn->resolve_async.resolver, + (pfd[i].revents & (POLLRDNORM|POLLIN))? + pfd[i].fd:ARES_SOCKET_BAD, + (pfd[i].revents & (POLLWRNORM|POLLOUT))? + pfd[i].fd:ARES_SOCKET_BAD); + } + return nfds; +} + +/* + * Curl_resolver_is_resolved() is called repeatedly to check if a previous + * name resolve request has completed. It should also make sure to time-out if + * the operation seems to take too long. + * + * Returns normal CURLcode errors. + */ +CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dns) +{ + struct thread_data *res = data->conn->resolve_async.tdata; + CURLcode result = CURLE_OK; + + DEBUGASSERT(dns); + *dns = NULL; + + if(waitperform(data, 0) < 0) + return CURLE_UNRECOVERABLE_POLL; + +#ifndef HAVE_CARES_GETADDRINFO + /* Now that we've checked for any last minute results above, see if there are + any responses still pending when the EXPIRE_HAPPY_EYEBALLS_DNS timer + expires. */ + if(res + && res->num_pending + /* This is only set to non-zero if the timer was started. */ + && (res->happy_eyeballs_dns_time.tv_sec + || res->happy_eyeballs_dns_time.tv_usec) + && (Curl_timediff(Curl_now(), res->happy_eyeballs_dns_time) + >= HAPPY_EYEBALLS_DNS_TIMEOUT)) { + /* Remember that the EXPIRE_HAPPY_EYEBALLS_DNS timer is no longer + running. */ + memset( + &res->happy_eyeballs_dns_time, 0, sizeof(res->happy_eyeballs_dns_time)); + + /* Cancel the raw c-ares request, which will fire query_completed_cb() with + ARES_ECANCELLED synchronously for all pending responses. This will + leave us with res->num_pending == 0, which is perfect for the next + block. */ + ares_cancel((ares_channel)data->conn->resolve_async.resolver); + DEBUGASSERT(res->num_pending == 0); + } +#endif + + if(res && !res->num_pending) { + (void)Curl_addrinfo_callback(data, res->last_status, res->temp_ai); + /* temp_ai ownership is moved to the connection, so we need not free-up + them */ + res->temp_ai = NULL; + + if(!data->conn->resolve_async.dns) + result = Curl_resolver_error(data); + else + *dns = data->conn->resolve_async.dns; + + destroy_async_data(&data->conn->resolve_async); + } + + return result; +} + +/* + * Curl_resolver_wait_resolv() + * + * Waits for a resolve to finish. This function should be avoided since using + * this risk getting the multi interface to "hang". + * + * 'entry' MUST be non-NULL. + * + * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, + * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. + */ +CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, + struct Curl_dns_entry **entry) +{ + CURLcode result = CURLE_OK; + timediff_t timeout; + struct curltime now = Curl_now(); + + DEBUGASSERT(entry); + *entry = NULL; /* clear on entry */ + + timeout = Curl_timeleft(data, &now, TRUE); + if(timeout < 0) { + /* already expired! */ + connclose(data->conn, "Timed out before name resolve started"); + return CURLE_OPERATION_TIMEDOUT; + } + if(!timeout) + timeout = CURL_TIMEOUT_RESOLVE * 1000; /* default name resolve timeout */ + + /* Wait for the name resolve query to complete. */ + while(!result) { + struct timeval *tvp, tv, store; + int itimeout; + timediff_t timeout_ms; + +#if TIMEDIFF_T_MAX > INT_MAX + itimeout = (timeout > INT_MAX) ? INT_MAX : (int)timeout; +#else + itimeout = (int)timeout; +#endif + + store.tv_sec = itimeout/1000; + store.tv_usec = (itimeout%1000)*1000; + + tvp = ares_timeout((ares_channel)data->conn->resolve_async.resolver, + &store, &tv); + + /* use the timeout period ares returned to us above if less than one + second is left, otherwise just use 1000ms to make sure the progress + callback gets called frequent enough */ + if(!tvp->tv_sec) + timeout_ms = (timediff_t)(tvp->tv_usec/1000); + else + timeout_ms = 1000; + + if(waitperform(data, timeout_ms) < 0) + return CURLE_UNRECOVERABLE_POLL; + result = Curl_resolver_is_resolved(data, entry); + + if(result || data->conn->resolve_async.done) + break; + + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else { + struct curltime now2 = Curl_now(); + timediff_t timediff = Curl_timediff(now2, now); /* spent time */ + if(timediff <= 0) + timeout -= 1; /* always deduct at least 1 */ + else if(timediff > timeout) + timeout = -1; + else + timeout -= timediff; + now = now2; /* for next loop */ + } + if(timeout < 0) + result = CURLE_OPERATION_TIMEDOUT; + } + if(result) + /* failure, so we cancel the ares operation */ + ares_cancel((ares_channel)data->conn->resolve_async.resolver); + + /* Operation complete, if the lookup was successful we now have the entry + in the cache. */ + if(entry) + *entry = data->conn->resolve_async.dns; + + if(result) + /* close the connection, since we can't return failure here without + cleaning up this connection properly. */ + connclose(data->conn, "c-ares resolve failed"); + + return result; +} + +#ifndef HAVE_CARES_GETADDRINFO + +/* Connects results to the list */ +static void compound_results(struct thread_data *res, + struct Curl_addrinfo *ai) +{ + if(!ai) + return; + +#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ + if(res->temp_ai && res->temp_ai->ai_family == PF_INET6) { + /* We have results already, put the new IPv6 entries at the head of the + list. */ + struct Curl_addrinfo *temp_ai_tail = res->temp_ai; + + while(temp_ai_tail->ai_next) + temp_ai_tail = temp_ai_tail->ai_next; + + temp_ai_tail->ai_next = ai; + } + else +#endif /* CURLRES_IPV6 */ + { + /* Add the new results to the list of old results. */ + struct Curl_addrinfo *ai_tail = ai; + while(ai_tail->ai_next) + ai_tail = ai_tail->ai_next; + + ai_tail->ai_next = res->temp_ai; + res->temp_ai = ai; + } +} + +/* + * ares_query_completed_cb() is the callback that ares will call when + * the host query initiated by ares_gethostbyname() from Curl_getaddrinfo(), + * when using ares, is completed either successfully or with failure. + */ +static void query_completed_cb(void *arg, /* (struct connectdata *) */ + int status, +#ifdef HAVE_CARES_CALLBACK_TIMEOUTS + int timeouts, +#endif + struct hostent *hostent) +{ + struct Curl_easy *data = (struct Curl_easy *)arg; + struct thread_data *res; + +#ifdef HAVE_CARES_CALLBACK_TIMEOUTS + (void)timeouts; /* ignored */ +#endif + + if(ARES_EDESTRUCTION == status) + /* when this ares handle is getting destroyed, the 'arg' pointer may not + be valid so only defer it when we know the 'status' says its fine! */ + return; + + res = data->conn->resolve_async.tdata; + if(res) { + res->num_pending--; + + if(CURL_ASYNC_SUCCESS == status) { + struct Curl_addrinfo *ai = Curl_he2ai(hostent, + data->conn->resolve_async.port); + if(ai) { + compound_results(res, ai); + } + } + /* A successful result overwrites any previous error */ + if(res->last_status != ARES_SUCCESS) + res->last_status = status; + + /* If there are responses still pending, we presume they must be the + complementary IPv4 or IPv6 lookups that we started in parallel in + Curl_resolver_getaddrinfo() (for Happy Eyeballs). If we've got a + "definitive" response from one of a set of parallel queries, we need to + think about how long we're willing to wait for more responses. */ + if(res->num_pending + /* Only these c-ares status values count as "definitive" for these + purposes. For example, ARES_ENODATA is what we expect when there is + no IPv6 entry for a domain name, and that's not a reason to get more + aggressive in our timeouts for the other response. Other errors are + either a result of bad input (which should affect all parallel + requests), local or network conditions, non-definitive server + responses, or us cancelling the request. */ + && (status == ARES_SUCCESS || status == ARES_ENOTFOUND)) { + /* Right now, there can only be up to two parallel queries, so don't + bother handling any other cases. */ + DEBUGASSERT(res->num_pending == 1); + + /* It's possible that one of these parallel queries could succeed + quickly, but the other could always fail or timeout (when we're + talking to a pool of DNS servers that can only successfully resolve + IPv4 address, for example). + + It's also possible that the other request could always just take + longer because it needs more time or only the second DNS server can + fulfill it successfully. But, to align with the philosophy of Happy + Eyeballs, we don't want to wait _too_ long or users will think + requests are slow when IPv6 lookups don't actually work (but IPv4 ones + do). + + So, now that we have a usable answer (some IPv4 addresses, some IPv6 + addresses, or "no such domain"), we start a timeout for the remaining + pending responses. Even though it is typical that this resolved + request came back quickly, that needn't be the case. It might be that + this completing request didn't get a result from the first DNS server + or even the first round of the whole DNS server pool. So it could + already be quite some time after we issued the DNS queries in the + first place. Without modifying c-ares, we can't know exactly where in + its retry cycle we are. We could guess based on how much time has + gone by, but it doesn't really matter. Happy Eyeballs tells us that, + given usable information in hand, we simply don't want to wait "too + much longer" after we get a result. + + We simply wait an additional amount of time equal to the default + c-ares query timeout. That is enough time for a typical parallel + response to arrive without being "too long". Even on a network + where one of the two types of queries is failing or timing out + constantly, this will usually mean we wait a total of the default + c-ares timeout (5 seconds) plus the round trip time for the successful + request, which seems bearable. The downside is that c-ares might race + with us to issue one more retry just before we give up, but it seems + better to "waste" that request instead of trying to guess the perfect + timeout to prevent it. After all, we don't even know where in the + c-ares retry cycle each request is. + */ + res->happy_eyeballs_dns_time = Curl_now(); + Curl_expire(data, HAPPY_EYEBALLS_DNS_TIMEOUT, + EXPIRE_HAPPY_EYEBALLS_DNS); + } + } +} +#else +/* c-ares 1.16.0 or later */ + +/* + * ares2addr() converts an address list provided by c-ares to an internal + * libcurl compatible list + */ +static struct Curl_addrinfo *ares2addr(struct ares_addrinfo_node *node) +{ + /* traverse the ares_addrinfo_node list */ + struct ares_addrinfo_node *ai; + struct Curl_addrinfo *cafirst = NULL; + struct Curl_addrinfo *calast = NULL; + int error = 0; + + for(ai = node; ai != NULL; ai = ai->ai_next) { + size_t ss_size; + struct Curl_addrinfo *ca; + /* ignore elements with unsupported address family, */ + /* settle family-specific sockaddr structure size. */ + if(ai->ai_family == AF_INET) + ss_size = sizeof(struct sockaddr_in); +#ifdef ENABLE_IPV6 + else if(ai->ai_family == AF_INET6) + ss_size = sizeof(struct sockaddr_in6); +#endif + else + continue; + + /* ignore elements without required address info */ + if(!ai->ai_addr || !(ai->ai_addrlen > 0)) + continue; + + /* ignore elements with bogus address size */ + if((size_t)ai->ai_addrlen < ss_size) + continue; + + ca = malloc(sizeof(struct Curl_addrinfo) + ss_size); + if(!ca) { + error = EAI_MEMORY; + break; + } + + /* copy each structure member individually, member ordering, */ + /* size, or padding might be different for each platform. */ + + ca->ai_flags = ai->ai_flags; + ca->ai_family = ai->ai_family; + ca->ai_socktype = ai->ai_socktype; + ca->ai_protocol = ai->ai_protocol; + ca->ai_addrlen = (curl_socklen_t)ss_size; + ca->ai_addr = NULL; + ca->ai_canonname = NULL; + ca->ai_next = NULL; + + ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo)); + memcpy(ca->ai_addr, ai->ai_addr, ss_size); + + /* if the return list is empty, this becomes the first element */ + if(!cafirst) + cafirst = ca; + + /* add this element last in the return list */ + if(calast) + calast->ai_next = ca; + calast = ca; + } + + /* if we failed, destroy the Curl_addrinfo list */ + if(error) { + Curl_freeaddrinfo(cafirst); + cafirst = NULL; + } + + return cafirst; +} + +static void addrinfo_cb(void *arg, int status, int timeouts, + struct ares_addrinfo *result) +{ + struct Curl_easy *data = (struct Curl_easy *)arg; + if(data->conn) { + struct thread_data *res = data->conn->resolve_async.tdata; + (void)timeouts; + if(ARES_SUCCESS == status) { + res->temp_ai = ares2addr(result->nodes); + res->last_status = CURL_ASYNC_SUCCESS; + ares_freeaddrinfo(result); + } + res->num_pending--; + } +} + +#endif +/* + * Curl_resolver_getaddrinfo() - when using ares + * + * Returns name information about the given hostname and port number. If + * successful, the 'hostent' is returned and the fourth argument will point to + * memory we need to free after use. That memory *MUST* be freed with + * Curl_freeaddrinfo(), nothing else. + */ +struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) +{ + struct thread_data *res = NULL; + size_t namelen = strlen(hostname); + *waitp = 0; /* default to synchronous response */ + + res = calloc(1, sizeof(struct thread_data) + namelen); + if(res) { + strcpy(res->hostname, hostname); + data->conn->resolve_async.hostname = res->hostname; + data->conn->resolve_async.port = port; + data->conn->resolve_async.done = FALSE; /* not done */ + data->conn->resolve_async.status = 0; /* clear */ + data->conn->resolve_async.dns = NULL; /* clear */ + data->conn->resolve_async.tdata = res; + + /* initial status - failed */ + res->last_status = ARES_ENOTFOUND; + +#ifdef HAVE_CARES_GETADDRINFO + { + struct ares_addrinfo_hints hints; + char service[12]; + int pf = PF_INET; + memset(&hints, 0, sizeof(hints)); +#ifdef CURLRES_IPV6 + if((data->conn->ip_version != CURL_IPRESOLVE_V4) && + Curl_ipv6works(data)) { + /* The stack seems to be IPv6-enabled */ + if(data->conn->ip_version == CURL_IPRESOLVE_V6) + pf = PF_INET6; + else + pf = PF_UNSPEC; + } +#endif /* CURLRES_IPV6 */ + hints.ai_family = pf; + hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP)? + SOCK_STREAM : SOCK_DGRAM; + /* Since the service is a numerical one, set the hint flags + * accordingly to save a call to getservbyname in inside C-Ares + */ + hints.ai_flags = ARES_AI_NUMERICSERV; + msnprintf(service, sizeof(service), "%d", port); + res->num_pending = 1; + ares_getaddrinfo((ares_channel)data->conn->resolve_async.resolver, + hostname, service, &hints, addrinfo_cb, data); + } +#else + +#ifdef HAVE_CARES_IPV6 + if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + /* The stack seems to be IPv6-enabled */ + res->num_pending = 2; + + /* areschannel is already setup in the Curl_open() function */ + ares_gethostbyname((ares_channel)data->conn->resolve_async.resolver, + hostname, PF_INET, query_completed_cb, data); + ares_gethostbyname((ares_channel)data->conn->resolve_async.resolver, + hostname, PF_INET6, query_completed_cb, data); + } + else +#endif + { + res->num_pending = 1; + + /* areschannel is already setup in the Curl_open() function */ + ares_gethostbyname((ares_channel)data->conn->resolve_async.resolver, + hostname, PF_INET, + query_completed_cb, data); + } +#endif + *waitp = 1; /* expect asynchronous response */ + } + return NULL; /* no struct yet */ +} + +CURLcode Curl_set_dns_servers(struct Curl_easy *data, + char *servers) +{ + CURLcode result = CURLE_NOT_BUILT_IN; + ares_channel channel, lchannel = NULL; + int ares_result; + + /* If server is NULL or empty, this would purge all DNS servers + * from ares library, which will cause any and all queries to fail. + * So, just return OK if none are configured and don't actually make + * any changes to c-ares. This lets c-ares use it's defaults, which + * it gets from the OS (for instance from /etc/resolv.conf on Linux). + */ + if(!(servers && servers[0])) + return CURLE_OK; + +#ifdef HAVE_CARES_SERVERS_CSV + if(data->conn) + channel = data->conn->resolve_async.resolver; + else { + /* we are called by setopt on a data without a connection (yet). In that + * case we set the value on a local instance for checking. + * The configured data options are set when the connection for this + * transfer is created. */ + result = Curl_resolver_init(data, (void **)&lchannel); + if(result) + goto out; + channel = lchannel; + } + +#ifdef HAVE_CARES_PORTS_CSV + ares_result = ares_set_servers_ports_csv(channel, servers); +#else + ares_result = ares_set_servers_csv(channel, servers); +#endif + switch(ares_result) { + case ARES_SUCCESS: + result = CURLE_OK; + break; + case ARES_ENOMEM: + result = CURLE_OUT_OF_MEMORY; + break; + case ARES_ENOTINITIALIZED: + case ARES_ENODATA: + case ARES_EBADSTR: + default: + result = CURLE_BAD_FUNCTION_ARGUMENT; + break; + } +out: + if(lchannel) + Curl_resolver_cleanup(lchannel); +#else /* too old c-ares version! */ + (void)data; + (void)(ares_result); +#endif + return result; +} + +CURLcode Curl_set_dns_interface(struct Curl_easy *data, + const char *interf) +{ +#ifdef HAVE_CARES_LOCAL_DEV + if(data->conn) { + /* not a setopt test run, set the value */ + if(!interf) + interf = ""; + + ares_set_local_dev((ares_channel)data->conn->resolve_async.resolver, + interf); + } + return CURLE_OK; +#else /* c-ares version too old! */ + (void)data; + (void)interf; + return CURLE_NOT_BUILT_IN; +#endif +} + +CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, + const char *local_ip4) +{ +#ifdef HAVE_CARES_SET_LOCAL + struct in_addr a4; + + if((!local_ip4) || (local_ip4[0] == 0)) { + a4.s_addr = 0; /* disabled: do not bind to a specific address */ + } + else { + if(Curl_inet_pton(AF_INET, local_ip4, &a4) != 1) { + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } + + if(data->conn) { + /* not a setopt test run, set the value */ + ares_set_local_ip4((ares_channel)data->conn->resolve_async.resolver, + ntohl(a4.s_addr)); + } + + return CURLE_OK; +#else /* c-ares version too old! */ + (void)data; + (void)local_ip4; + return CURLE_NOT_BUILT_IN; +#endif +} + +CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, + const char *local_ip6) +{ +#if defined(HAVE_CARES_SET_LOCAL) && defined(ENABLE_IPV6) + unsigned char a6[INET6_ADDRSTRLEN]; + + if((!local_ip6) || (local_ip6[0] == 0)) { + /* disabled: do not bind to a specific address */ + memset(a6, 0, sizeof(a6)); + } + else { + if(Curl_inet_pton(AF_INET6, local_ip6, a6) != 1) { + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } + + if(data->conn) { + /* not a setopt test run, set the value */ + ares_set_local_ip6((ares_channel)data->conn->resolve_async.resolver, a6); + } + + return CURLE_OK; +#else /* c-ares version too old! */ + (void)data; + (void)local_ip6; + return CURLE_NOT_BUILT_IN; +#endif +} +#endif /* CURLRES_ARES */ diff --git a/Utilities/cmcurl/lib/asyn-thread.c b/Utilities/cmcurl/lib/asyn-thread.c new file mode 100644 index 0000000..63414b6 --- /dev/null +++ b/Utilities/cmcurl/lib/asyn-thread.c @@ -0,0 +1,760 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "socketpair.h" + +/*********************************************************************** + * Only for threaded name resolves builds + **********************************************************************/ +#ifdef CURLRES_THREADED + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) +# include <pthread.h> +#endif + +#ifdef HAVE_GETADDRINFO +# define RESOLVER_ENOMEM EAI_MEMORY +#else +# define RESOLVER_ENOMEM ENOMEM +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "url.h" +#include "multiif.h" +#include "inet_ntop.h" +#include "curl_threads.h" +#include "connect.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +struct resdata { + struct curltime start; +}; + +/* + * Curl_resolver_global_init() + * Called from curl_global_init() to initialize global resolver environment. + * Does nothing here. + */ +int Curl_resolver_global_init(void) +{ + return CURLE_OK; +} + +/* + * Curl_resolver_global_cleanup() + * Called from curl_global_cleanup() to destroy global resolver environment. + * Does nothing here. + */ +void Curl_resolver_global_cleanup(void) +{ +} + +/* + * Curl_resolver_init() + * Called from curl_easy_init() -> Curl_open() to initialize resolver + * URL-state specific environment ('resolver' member of the UrlState + * structure). + */ +CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) +{ + (void)easy; + *resolver = calloc(1, sizeof(struct resdata)); + if(!*resolver) + return CURLE_OUT_OF_MEMORY; + return CURLE_OK; +} + +/* + * Curl_resolver_cleanup() + * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver + * URL-state specific environment ('resolver' member of the UrlState + * structure). + */ +void Curl_resolver_cleanup(void *resolver) +{ + free(resolver); +} + +/* + * Curl_resolver_duphandle() + * Called from curl_easy_duphandle() to duplicate resolver URL state-specific + * environment ('resolver' member of the UrlState structure). + */ +CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from) +{ + (void)from; + return Curl_resolver_init(easy, to); +} + +static void destroy_async_data(struct Curl_async *); + +/* + * Cancel all possibly still on-going resolves for this connection. + */ +void Curl_resolver_cancel(struct Curl_easy *data) +{ + destroy_async_data(&data->conn->resolve_async); +} + +/* This function is used to init a threaded resolve */ +static bool init_resolve_thread(struct Curl_easy *data, + const char *hostname, int port, + const struct addrinfo *hints); + + +/* Data for synchronization between resolver thread and its parent */ +struct thread_sync_data { + curl_mutex_t *mtx; + int done; + int port; + char *hostname; /* hostname to resolve, Curl_async.hostname + duplicate */ +#ifndef CURL_DISABLE_SOCKETPAIR + struct Curl_easy *data; + curl_socket_t sock_pair[2]; /* socket pair */ +#endif + int sock_error; + struct Curl_addrinfo *res; +#ifdef HAVE_GETADDRINFO + struct addrinfo hints; +#endif + struct thread_data *td; /* for thread-self cleanup */ +}; + +struct thread_data { + curl_thread_t thread_hnd; + unsigned int poll_interval; + timediff_t interval_end; + struct thread_sync_data tsd; +}; + +static struct thread_sync_data *conn_thread_sync_data(struct Curl_easy *data) +{ + return &(data->conn->resolve_async.tdata->tsd); +} + +/* Destroy resolver thread synchronization data */ +static +void destroy_thread_sync_data(struct thread_sync_data *tsd) +{ + if(tsd->mtx) { + Curl_mutex_destroy(tsd->mtx); + free(tsd->mtx); + } + + free(tsd->hostname); + + if(tsd->res) + Curl_freeaddrinfo(tsd->res); + +#ifndef CURL_DISABLE_SOCKETPAIR + /* + * close one end of the socket pair (may be done in resolver thread); + * the other end (for reading) is always closed in the parent thread. + */ + if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { + wakeup_close(tsd->sock_pair[1]); + } +#endif + memset(tsd, 0, sizeof(*tsd)); +} + +/* Initialize resolver thread synchronization data */ +static +int init_thread_sync_data(struct thread_data *td, + const char *hostname, + int port, + const struct addrinfo *hints) +{ + struct thread_sync_data *tsd = &td->tsd; + + memset(tsd, 0, sizeof(*tsd)); + + tsd->td = td; + tsd->port = port; + /* Treat the request as done until the thread actually starts so any early + * cleanup gets done properly. + */ + tsd->done = 1; +#ifdef HAVE_GETADDRINFO + DEBUGASSERT(hints); + tsd->hints = *hints; +#else + (void) hints; +#endif + + tsd->mtx = malloc(sizeof(curl_mutex_t)); + if(!tsd->mtx) + goto err_exit; + + Curl_mutex_init(tsd->mtx); + +#ifndef CURL_DISABLE_SOCKETPAIR + /* create socket pair or pipe */ + if(wakeup_create(&tsd->sock_pair[0]) < 0) { + tsd->sock_pair[0] = CURL_SOCKET_BAD; + tsd->sock_pair[1] = CURL_SOCKET_BAD; + goto err_exit; + } +#endif + tsd->sock_error = CURL_ASYNC_SUCCESS; + + /* Copying hostname string because original can be destroyed by parent + * thread during gethostbyname execution. + */ + tsd->hostname = strdup(hostname); + if(!tsd->hostname) + goto err_exit; + + return 1; + +err_exit: +#ifndef CURL_DISABLE_SOCKETPAIR + if(tsd->sock_pair[0] != CURL_SOCKET_BAD) { + wakeup_close(tsd->sock_pair[0]); + tsd->sock_pair[0] = CURL_SOCKET_BAD; + } +#endif + destroy_thread_sync_data(tsd); + return 0; +} + +static CURLcode getaddrinfo_complete(struct Curl_easy *data) +{ + struct thread_sync_data *tsd = conn_thread_sync_data(data); + CURLcode result; + + result = Curl_addrinfo_callback(data, tsd->sock_error, tsd->res); + /* The tsd->res structure has been copied to async.dns and perhaps the DNS + cache. Set our copy to NULL so destroy_thread_sync_data doesn't free it. + */ + tsd->res = NULL; + + return result; +} + + +#ifdef HAVE_GETADDRINFO + +/* + * getaddrinfo_thread() resolves a name and then exits. + * + * For builds without ARES, but with ENABLE_IPV6, create a resolver thread + * and wait on it. + */ +static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg) +{ + struct thread_sync_data *tsd = (struct thread_sync_data *)arg; + struct thread_data *td = tsd->td; + char service[12]; + int rc; +#ifndef CURL_DISABLE_SOCKETPAIR + char buf[1]; +#endif + + msnprintf(service, sizeof(service), "%d", tsd->port); + + rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res); + + if(rc) { + tsd->sock_error = SOCKERRNO?SOCKERRNO:rc; + if(tsd->sock_error == 0) + tsd->sock_error = RESOLVER_ENOMEM; + } + else { + Curl_addrinfo_set_port(tsd->res, tsd->port); + } + + Curl_mutex_acquire(tsd->mtx); + if(tsd->done) { + /* too late, gotta clean up the mess */ + Curl_mutex_release(tsd->mtx); + destroy_thread_sync_data(tsd); + free(td); + } + else { +#ifndef CURL_DISABLE_SOCKETPAIR + if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { + /* DNS has been resolved, signal client task */ + buf[0] = 1; + if(wakeup_write(tsd->sock_pair[1], buf, sizeof(buf)) < 0) { + /* update sock_erro to errno */ + tsd->sock_error = SOCKERRNO; + } + } +#endif + tsd->done = 1; + Curl_mutex_release(tsd->mtx); + } + + return 0; +} + +#else /* HAVE_GETADDRINFO */ + +/* + * gethostbyname_thread() resolves a name and then exits. + */ +static unsigned int CURL_STDCALL gethostbyname_thread(void *arg) +{ + struct thread_sync_data *tsd = (struct thread_sync_data *)arg; + struct thread_data *td = tsd->td; + + tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port); + + if(!tsd->res) { + tsd->sock_error = SOCKERRNO; + if(tsd->sock_error == 0) + tsd->sock_error = RESOLVER_ENOMEM; + } + + Curl_mutex_acquire(tsd->mtx); + if(tsd->done) { + /* too late, gotta clean up the mess */ + Curl_mutex_release(tsd->mtx); + destroy_thread_sync_data(tsd); + free(td); + } + else { + tsd->done = 1; + Curl_mutex_release(tsd->mtx); + } + + return 0; +} + +#endif /* HAVE_GETADDRINFO */ + +/* + * destroy_async_data() cleans up async resolver data and thread handle. + */ +static void destroy_async_data(struct Curl_async *async) +{ + if(async->tdata) { + struct thread_data *td = async->tdata; + int done; +#ifndef CURL_DISABLE_SOCKETPAIR + curl_socket_t sock_rd = td->tsd.sock_pair[0]; + struct Curl_easy *data = td->tsd.data; +#endif + + /* + * if the thread is still blocking in the resolve syscall, detach it and + * let the thread do the cleanup... + */ + Curl_mutex_acquire(td->tsd.mtx); + done = td->tsd.done; + td->tsd.done = 1; + Curl_mutex_release(td->tsd.mtx); + + if(!done) { + Curl_thread_destroy(td->thread_hnd); + } + else { + if(td->thread_hnd != curl_thread_t_null) + Curl_thread_join(&td->thread_hnd); + + destroy_thread_sync_data(&td->tsd); + + free(async->tdata); + } +#ifndef CURL_DISABLE_SOCKETPAIR + /* + * ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE + * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL + */ + Curl_multi_closed(data, sock_rd); + sclose(sock_rd); +#endif + } + async->tdata = NULL; + + free(async->hostname); + async->hostname = NULL; +} + +/* + * init_resolve_thread() starts a new thread that performs the actual + * resolve. This function returns before the resolve is done. + * + * Returns FALSE in case of failure, otherwise TRUE. + */ +static bool init_resolve_thread(struct Curl_easy *data, + const char *hostname, int port, + const struct addrinfo *hints) +{ + struct thread_data *td = calloc(1, sizeof(struct thread_data)); + int err = ENOMEM; + struct Curl_async *asp = &data->conn->resolve_async; + + data->conn->resolve_async.tdata = td; + if(!td) + goto errno_exit; + + asp->port = port; + asp->done = FALSE; + asp->status = 0; + asp->dns = NULL; + td->thread_hnd = curl_thread_t_null; + + if(!init_thread_sync_data(td, hostname, port, hints)) { + asp->tdata = NULL; + free(td); + goto errno_exit; + } + + free(asp->hostname); + asp->hostname = strdup(hostname); + if(!asp->hostname) + goto err_exit; + + /* The thread will set this to 1 when complete. */ + td->tsd.done = 0; + +#ifdef HAVE_GETADDRINFO + td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd); +#else + td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd); +#endif + + if(!td->thread_hnd) { + /* The thread never started, so mark it as done here for proper cleanup. */ + td->tsd.done = 1; + err = errno; + goto err_exit; + } + + return TRUE; + +err_exit: + destroy_async_data(asp); + +errno_exit: + errno = err; + return FALSE; +} + +/* + * 'entry' may be NULL and then no data is returned + */ +static CURLcode thread_wait_resolv(struct Curl_easy *data, + struct Curl_dns_entry **entry, + bool report) +{ + struct thread_data *td; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + td = data->conn->resolve_async.tdata; + DEBUGASSERT(td); + DEBUGASSERT(td->thread_hnd != curl_thread_t_null); + + /* wait for the thread to resolve the name */ + if(Curl_thread_join(&td->thread_hnd)) { + if(entry) + result = getaddrinfo_complete(data); + } + else + DEBUGASSERT(0); + + data->conn->resolve_async.done = TRUE; + + if(entry) + *entry = data->conn->resolve_async.dns; + + if(!data->conn->resolve_async.dns && report) + /* a name was not resolved, report error */ + result = Curl_resolver_error(data); + + destroy_async_data(&data->conn->resolve_async); + + if(!data->conn->resolve_async.dns && report) + connclose(data->conn, "asynch resolve failed"); + + return result; +} + + +/* + * Until we gain a way to signal the resolver threads to stop early, we must + * simply wait for them and ignore their results. + */ +void Curl_resolver_kill(struct Curl_easy *data) +{ + struct thread_data *td = data->conn->resolve_async.tdata; + + /* If we're still resolving, we must wait for the threads to fully clean up, + unfortunately. Otherwise, we can simply cancel to clean up any resolver + data. */ + if(td && td->thread_hnd != curl_thread_t_null + && (data->set.quick_exit != 1L)) + (void)thread_wait_resolv(data, NULL, FALSE); + else + Curl_resolver_cancel(data); +} + +/* + * Curl_resolver_wait_resolv() + * + * Waits for a resolve to finish. This function should be avoided since using + * this risk getting the multi interface to "hang". + * + * If 'entry' is non-NULL, make it point to the resolved dns entry + * + * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, + * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. + * + * This is the version for resolves-in-a-thread. + */ +CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, + struct Curl_dns_entry **entry) +{ + return thread_wait_resolv(data, entry, TRUE); +} + +/* + * Curl_resolver_is_resolved() is called repeatedly to check if a previous + * name resolve request has completed. It should also make sure to time-out if + * the operation seems to take too long. + */ +CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **entry) +{ + struct thread_data *td = data->conn->resolve_async.tdata; + int done = 0; + + DEBUGASSERT(entry); + *entry = NULL; + + if(!td) { + DEBUGASSERT(td); + return CURLE_COULDNT_RESOLVE_HOST; + } + + Curl_mutex_acquire(td->tsd.mtx); + done = td->tsd.done; + Curl_mutex_release(td->tsd.mtx); + + if(done) { + getaddrinfo_complete(data); + + if(!data->conn->resolve_async.dns) { + CURLcode result = Curl_resolver_error(data); + destroy_async_data(&data->conn->resolve_async); + return result; + } + destroy_async_data(&data->conn->resolve_async); + *entry = data->conn->resolve_async.dns; + } + else { + /* poll for name lookup done with exponential backoff up to 250ms */ + /* should be fine even if this converts to 32 bit */ + timediff_t elapsed = Curl_timediff(Curl_now(), + data->progress.t_startsingle); + if(elapsed < 0) + elapsed = 0; + + if(td->poll_interval == 0) + /* Start at 1ms poll interval */ + td->poll_interval = 1; + else if(elapsed >= td->interval_end) + /* Back-off exponentially if last interval expired */ + td->poll_interval *= 2; + + if(td->poll_interval > 250) + td->poll_interval = 250; + + td->interval_end = elapsed + td->poll_interval; + Curl_expire(data, td->poll_interval, EXPIRE_ASYNC_NAME); + } + + return CURLE_OK; +} + +int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks) +{ + int ret_val = 0; + timediff_t milli; + timediff_t ms; + struct resdata *reslv = (struct resdata *)data->conn->resolve_async.resolver; +#ifndef CURL_DISABLE_SOCKETPAIR + struct thread_data *td = data->conn->resolve_async.tdata; +#else + (void)socks; +#endif + +#ifndef CURL_DISABLE_SOCKETPAIR + if(td) { + /* return read fd to client for polling the DNS resolution status */ + socks[0] = td->tsd.sock_pair[0]; + td->tsd.data = data; + ret_val = GETSOCK_READSOCK(0); + } + else { +#endif + ms = Curl_timediff(Curl_now(), reslv->start); + if(ms < 3) + milli = 0; + else if(ms <= 50) + milli = ms/3; + else if(ms <= 250) + milli = 50; + else + milli = 200; + Curl_expire(data, milli, EXPIRE_ASYNC_NAME); +#ifndef CURL_DISABLE_SOCKETPAIR + } +#endif + + + return ret_val; +} + +#ifndef HAVE_GETADDRINFO +/* + * Curl_getaddrinfo() - for platforms without getaddrinfo + */ +struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) +{ + struct resdata *reslv = (struct resdata *)data->conn->resolve_async.resolver; + + *waitp = 0; /* default to synchronous response */ + + reslv->start = Curl_now(); + + /* fire up a new resolver thread! */ + if(init_resolve_thread(data, hostname, port, NULL)) { + *waitp = 1; /* expect asynchronous response */ + return NULL; + } + + failf(data, "getaddrinfo() thread failed"); + + return NULL; +} + +#else /* !HAVE_GETADDRINFO */ + +/* + * Curl_resolver_getaddrinfo() - for getaddrinfo + */ +struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) +{ + struct addrinfo hints; + int pf = PF_INET; + struct resdata *reslv = (struct resdata *)data->conn->resolve_async.resolver; + + *waitp = 0; /* default to synchronous response */ + +#ifdef CURLRES_IPV6 + if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + /* The stack seems to be IPv6-enabled */ + if(data->conn->ip_version == CURL_IPRESOLVE_V6) + pf = PF_INET6; + else + pf = PF_UNSPEC; + } +#endif /* CURLRES_IPV6 */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = pf; + hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP)? + SOCK_STREAM : SOCK_DGRAM; + + reslv->start = Curl_now(); + /* fire up a new resolver thread! */ + if(init_resolve_thread(data, hostname, port, &hints)) { + *waitp = 1; /* expect asynchronous response */ + return NULL; + } + + failf(data, "getaddrinfo() thread failed to start"); + return NULL; + +} + +#endif /* !HAVE_GETADDRINFO */ + +CURLcode Curl_set_dns_servers(struct Curl_easy *data, + char *servers) +{ + (void)data; + (void)servers; + return CURLE_NOT_BUILT_IN; + +} + +CURLcode Curl_set_dns_interface(struct Curl_easy *data, + const char *interf) +{ + (void)data; + (void)interf; + return CURLE_NOT_BUILT_IN; +} + +CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, + const char *local_ip4) +{ + (void)data; + (void)local_ip4; + return CURLE_NOT_BUILT_IN; +} + +CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, + const char *local_ip6) +{ + (void)data; + (void)local_ip6; + return CURLE_NOT_BUILT_IN; +} + +#endif /* CURLRES_THREADED */ diff --git a/Utilities/cmcurl/lib/asyn.h b/Utilities/cmcurl/lib/asyn.h new file mode 100644 index 0000000..7e207c4 --- /dev/null +++ b/Utilities/cmcurl/lib/asyn.h @@ -0,0 +1,184 @@ +#ifndef HEADER_CURL_ASYN_H +#define HEADER_CURL_ASYN_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "curl_addrinfo.h" + +struct addrinfo; +struct hostent; +struct Curl_easy; +struct connectdata; +struct Curl_dns_entry; + +/* + * This header defines all functions in the internal asynch resolver interface. + * All asynch resolvers need to provide these functions. + * asyn-ares.c and asyn-thread.c are the current implementations of asynch + * resolver backends. + */ + +/* + * Curl_resolver_global_init() + * + * Called from curl_global_init() to initialize global resolver environment. + * Returning anything else than CURLE_OK fails curl_global_init(). + */ +int Curl_resolver_global_init(void); + +/* + * Curl_resolver_global_cleanup() + * Called from curl_global_cleanup() to destroy global resolver environment. + */ +void Curl_resolver_global_cleanup(void); + +/* + * Curl_resolver_init() + * Called from curl_easy_init() -> Curl_open() to initialize resolver + * URL-state specific environment ('resolver' member of the UrlState + * structure). Should fill the passed pointer by the initialized handler. + * Returning anything else than CURLE_OK fails curl_easy_init() with the + * correspondent code. + */ +CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver); + +/* + * Curl_resolver_cleanup() + * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver + * URL-state specific environment ('resolver' member of the UrlState + * structure). Should destroy the handler and free all resources connected to + * it. + */ +void Curl_resolver_cleanup(void *resolver); + +/* + * Curl_resolver_duphandle() + * Called from curl_easy_duphandle() to duplicate resolver URL-state specific + * environment ('resolver' member of the UrlState structure). Should + * duplicate the 'from' handle and pass the resulting handle to the 'to' + * pointer. Returning anything else than CURLE_OK causes failed + * curl_easy_duphandle() call. + */ +CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, + void *from); + +/* + * Curl_resolver_cancel(). + * + * It is called from inside other functions to cancel currently performing + * resolver request. Should also free any temporary resources allocated to + * perform a request. This never waits for resolver threads to complete. + * + * It is safe to call this when conn is in any state. + */ +void Curl_resolver_cancel(struct Curl_easy *data); + +/* + * Curl_resolver_kill(). + * + * This acts like Curl_resolver_cancel() except it will block until any threads + * associated with the resolver are complete. This never blocks for resolvers + * that do not use threads. This is intended to be the "last chance" function + * that cleans up an in-progress resolver completely (before its owner is about + * to die). + * + * It is safe to call this when conn is in any state. + */ +void Curl_resolver_kill(struct Curl_easy *data); + +/* Curl_resolver_getsock() + * + * This function is called from the multi_getsock() function. 'sock' is a + * pointer to an array to hold the file descriptors, with 'numsock' being the + * size of that array (in number of entries). This function is supposed to + * return bitmask indicating what file descriptors (referring to array indexes + * in the 'sock' array) to wait for, read/write. + */ +int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *sock); + +/* + * Curl_resolver_is_resolved() + * + * Called repeatedly to check if a previous name resolve request has + * completed. It should also make sure to time-out if the operation seems to + * take too long. + * + * Returns normal CURLcode errors. + */ +CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dns); + +/* + * Curl_resolver_wait_resolv() + * + * Waits for a resolve to finish. This function should be avoided since using + * this risk getting the multi interface to "hang". + * + * If 'entry' is non-NULL, make it point to the resolved dns entry + * + * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, + * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. + */ +CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, + struct Curl_dns_entry **dnsentry); + +/* + * Curl_resolver_getaddrinfo() - when using this resolver + * + * Returns name information about the given hostname and port number. If + * successful, the 'hostent' is returned and the fourth argument will point to + * memory we need to free after use. That memory *MUST* be freed with + * Curl_freeaddrinfo(), nothing else. + * + * Each resolver backend must of course make sure to return data in the + * correct format to comply with this. + */ +struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp); + +#ifndef CURLRES_ASYNCH +/* convert these functions if an asynch resolver isn't used */ +#define Curl_resolver_cancel(x) Curl_nop_stmt +#define Curl_resolver_kill(x) Curl_nop_stmt +#define Curl_resolver_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST +#define Curl_resolver_wait_resolv(x,y) CURLE_COULDNT_RESOLVE_HOST +#define Curl_resolver_duphandle(x,y,z) CURLE_OK +#define Curl_resolver_init(x,y) CURLE_OK +#define Curl_resolver_global_init() CURLE_OK +#define Curl_resolver_global_cleanup() Curl_nop_stmt +#define Curl_resolver_cleanup(x) Curl_nop_stmt +#endif + +#ifdef CURLRES_ASYNCH +#define Curl_resolver_asynch() 1 +#else +#define Curl_resolver_asynch() 0 +#endif + + +/********** end of generic resolver interface functions *****************/ +#endif /* HEADER_CURL_ASYN_H */ diff --git a/Utilities/cmcurl/lib/base64.c b/Utilities/cmcurl/lib/base64.c new file mode 100644 index 0000000..919eb62 --- /dev/null +++ b/Utilities/cmcurl/lib/base64.c @@ -0,0 +1,293 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* Base64 encoding/decoding */ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP_AUTH) || defined(USE_SSH) || \ + !defined(CURL_DISABLE_LDAP) || \ + !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_POP3) || \ + !defined(CURL_DISABLE_IMAP) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_DOH) || defined(USE_SSL) || defined(BUILDING_CURL) +#include "curl/curl.h" +#include "warnless.h" +#include "curl_base64.h" + +/* The last 2 #include files should be in this order */ +#ifdef BUILDING_LIBCURL +#include "curl_memory.h" +#endif +#include "memdebug.h" + +/* ---- Base64 Encoding/Decoding Table --- */ +/* Padding character string starts at offset 64. */ +static const char base64encdec[]= + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + +/* The Base 64 encoding with a URL and filename safe alphabet, RFC 4648 + section 5 */ +static const char base64url[]= + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +static const unsigned char decodetable[] = +{ 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, + 255, 255, 255, 255, 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, 255, 255, 255, 255, 255, 255, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51 }; +/* + * Curl_base64_decode() + * + * Given a base64 NUL-terminated string at src, decode it and return a + * pointer in *outptr to a newly allocated memory area holding decoded + * data. Size of decoded data is returned in variable pointed by outlen. + * + * Returns CURLE_OK on success, otherwise specific error code. Function + * output shall not be considered valid unless CURLE_OK is returned. + * + * When decoded data length is 0, returns NULL in *outptr. + * + * @unittest: 1302 + */ +CURLcode Curl_base64_decode(const char *src, + unsigned char **outptr, size_t *outlen) +{ + size_t srclen = 0; + size_t padding = 0; + size_t i; + size_t numQuantums; + size_t fullQuantums; + size_t rawlen = 0; + unsigned char *pos; + unsigned char *newstr; + unsigned char lookup[256]; + + *outptr = NULL; + *outlen = 0; + srclen = strlen(src); + + /* Check the length of the input string is valid */ + if(!srclen || srclen % 4) + return CURLE_BAD_CONTENT_ENCODING; + + /* srclen is at least 4 here */ + while(src[srclen - 1 - padding] == '=') { + /* count padding characters */ + padding++; + /* A maximum of two = padding characters is allowed */ + if(padding > 2) + return CURLE_BAD_CONTENT_ENCODING; + } + + /* Calculate the number of quantums */ + numQuantums = srclen / 4; + fullQuantums = numQuantums - (padding ? 1 : 0); + + /* Calculate the size of the decoded string */ + rawlen = (numQuantums * 3) - padding; + + /* Allocate our buffer including room for a null-terminator */ + newstr = malloc(rawlen + 1); + if(!newstr) + return CURLE_OUT_OF_MEMORY; + + pos = newstr; + + memset(lookup, 0xff, sizeof(lookup)); + memcpy(&lookup['+'], decodetable, sizeof(decodetable)); + /* replaces + { + unsigned char c; + const unsigned char *p = (const unsigned char *)base64encdec; + for(c = 0; *p; c++, p++) + lookup[*p] = c; + } + */ + + /* Decode the complete quantums first */ + for(i = 0; i < fullQuantums; i++) { + unsigned char val; + unsigned int x = 0; + int j; + + for(j = 0; j < 4; j++) { + val = lookup[(unsigned char)*src++]; + if(val == 0xff) /* bad symbol */ + goto bad; + x = (x << 6) | val; + } + pos[2] = x & 0xff; + pos[1] = (x >> 8) & 0xff; + pos[0] = (x >> 16) & 0xff; + pos += 3; + } + if(padding) { + /* this means either 8 or 16 bits output */ + unsigned char val; + unsigned int x = 0; + int j; + size_t padc = 0; + for(j = 0; j < 4; j++) { + if(*src == '=') { + x <<= 6; + src++; + if(++padc > padding) + /* this is a badly placed '=' symbol! */ + goto bad; + } + else { + val = lookup[(unsigned char)*src++]; + if(val == 0xff) /* bad symbol */ + goto bad; + x = (x << 6) | val; + } + } + if(padding == 1) + pos[1] = (x >> 8) & 0xff; + pos[0] = (x >> 16) & 0xff; + pos += 3 - padding; + } + + /* Zero terminate */ + *pos = '\0'; + + /* Return the decoded data */ + *outptr = newstr; + *outlen = rawlen; + + return CURLE_OK; +bad: + free(newstr); + return CURLE_BAD_CONTENT_ENCODING; +} + +static CURLcode base64_encode(const char *table64, + const char *inputbuff, size_t insize, + char **outptr, size_t *outlen) +{ + char *output; + char *base64data; + const unsigned char *in = (unsigned char *)inputbuff; + const char *padstr = &table64[64]; /* Point to padding string. */ + + *outptr = NULL; + *outlen = 0; + + if(!insize) + insize = strlen(inputbuff); + +#if SIZEOF_SIZE_T == 4 + if(insize > UINT_MAX/4) + return CURLE_OUT_OF_MEMORY; +#endif + + base64data = output = malloc((insize + 2) / 3 * 4 + 1); + if(!output) + return CURLE_OUT_OF_MEMORY; + + while(insize >= 3) { + *output++ = table64[ in[0] >> 2 ]; + *output++ = table64[ ((in[0] & 0x03) << 4) | (in[1] >> 4) ]; + *output++ = table64[ ((in[1] & 0x0F) << 2) | ((in[2] & 0xC0) >> 6) ]; + *output++ = table64[ in[2] & 0x3F ]; + insize -= 3; + in += 3; + } + if(insize) { + /* this is only one or two bytes now */ + *output++ = table64[ in[0] >> 2 ]; + if(insize == 1) { + *output++ = table64[ ((in[0] & 0x03) << 4) ]; + if(*padstr) { + *output++ = *padstr; + *output++ = *padstr; + } + } + else { + /* insize == 2 */ + *output++ = table64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xF0) >> 4) ]; + *output++ = table64[ ((in[1] & 0x0F) << 2) ]; + if(*padstr) + *output++ = *padstr; + } + } + + /* Zero terminate */ + *output = '\0'; + + /* Return the pointer to the new data (allocated memory) */ + *outptr = base64data; + + /* Return the length of the new data */ + *outlen = output - base64data; + + return CURLE_OK; +} + +/* + * Curl_base64_encode() + * + * Given a pointer to an input buffer and an input size, encode it and + * return a pointer in *outptr to a newly allocated memory area holding + * encoded data. Size of encoded data is returned in variable pointed by + * outlen. + * + * Input length of 0 indicates input buffer holds a NUL-terminated string. + * + * Returns CURLE_OK on success, otherwise specific error code. Function + * output shall not be considered valid unless CURLE_OK is returned. + * + * @unittest: 1302 + */ +CURLcode Curl_base64_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen) +{ + return base64_encode(base64encdec, inputbuff, insize, outptr, outlen); +} + +/* + * Curl_base64url_encode() + * + * Given a pointer to an input buffer and an input size, encode it and + * return a pointer in *outptr to a newly allocated memory area holding + * encoded data. Size of encoded data is returned in variable pointed by + * outlen. + * + * Input length of 0 indicates input buffer holds a NUL-terminated string. + * + * Returns CURLE_OK on success, otherwise specific error code. Function + * output shall not be considered valid unless CURLE_OK is returned. + * + * @unittest: 1302 + */ +CURLcode Curl_base64url_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen) +{ + return base64_encode(base64url, inputbuff, insize, outptr, outlen); +} + +#endif /* no users so disabled */ diff --git a/Utilities/cmcurl/lib/bufq.c b/Utilities/cmcurl/lib/bufq.c new file mode 100644 index 0000000..d03906d --- /dev/null +++ b/Utilities/cmcurl/lib/bufq.c @@ -0,0 +1,656 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "bufq.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +static bool chunk_is_empty(const struct buf_chunk *chunk) +{ + return chunk->r_offset >= chunk->w_offset; +} + +static bool chunk_is_full(const struct buf_chunk *chunk) +{ + return chunk->w_offset >= chunk->dlen; +} + +static size_t chunk_len(const struct buf_chunk *chunk) +{ + return chunk->w_offset - chunk->r_offset; +} + +static size_t chunk_space(const struct buf_chunk *chunk) +{ + return chunk->dlen - chunk->w_offset; +} + +static void chunk_reset(struct buf_chunk *chunk) +{ + chunk->next = NULL; + chunk->r_offset = chunk->w_offset = 0; +} + +static size_t chunk_append(struct buf_chunk *chunk, + const unsigned char *buf, size_t len) +{ + unsigned char *p = &chunk->x.data[chunk->w_offset]; + size_t n = chunk->dlen - chunk->w_offset; + DEBUGASSERT(chunk->dlen >= chunk->w_offset); + if(n) { + n = CURLMIN(n, len); + memcpy(p, buf, n); + chunk->w_offset += n; + } + return n; +} + +static size_t chunk_read(struct buf_chunk *chunk, + unsigned char *buf, size_t len) +{ + unsigned char *p = &chunk->x.data[chunk->r_offset]; + size_t n = chunk->w_offset - chunk->r_offset; + DEBUGASSERT(chunk->w_offset >= chunk->r_offset); + if(!n) { + return 0; + } + else if(n <= len) { + memcpy(buf, p, n); + chunk->r_offset = chunk->w_offset = 0; + return n; + } + else { + memcpy(buf, p, len); + chunk->r_offset += len; + return len; + } +} + +static ssize_t chunk_slurpn(struct buf_chunk *chunk, size_t max_len, + Curl_bufq_reader *reader, + void *reader_ctx, CURLcode *err) +{ + unsigned char *p = &chunk->x.data[chunk->w_offset]; + size_t n = chunk->dlen - chunk->w_offset; /* free amount */ + ssize_t nread; + + DEBUGASSERT(chunk->dlen >= chunk->w_offset); + if(!n) { + *err = CURLE_AGAIN; + return -1; + } + if(max_len && n > max_len) + n = max_len; + nread = reader(reader_ctx, p, n, err); + if(nread > 0) { + DEBUGASSERT((size_t)nread <= n); + chunk->w_offset += nread; + } + return nread; +} + +static void chunk_peek(const struct buf_chunk *chunk, + const unsigned char **pbuf, size_t *plen) +{ + DEBUGASSERT(chunk->w_offset >= chunk->r_offset); + *pbuf = &chunk->x.data[chunk->r_offset]; + *plen = chunk->w_offset - chunk->r_offset; +} + +static void chunk_peek_at(const struct buf_chunk *chunk, size_t offset, + const unsigned char **pbuf, size_t *plen) +{ + offset += chunk->r_offset; + DEBUGASSERT(chunk->w_offset >= offset); + *pbuf = &chunk->x.data[offset]; + *plen = chunk->w_offset - offset; +} + +static size_t chunk_skip(struct buf_chunk *chunk, size_t amount) +{ + size_t n = chunk->w_offset - chunk->r_offset; + DEBUGASSERT(chunk->w_offset >= chunk->r_offset); + if(n) { + n = CURLMIN(n, amount); + chunk->r_offset += n; + if(chunk->r_offset == chunk->w_offset) + chunk->r_offset = chunk->w_offset = 0; + } + return n; +} + +static void chunk_list_free(struct buf_chunk **anchor) +{ + struct buf_chunk *chunk; + while(*anchor) { + chunk = *anchor; + *anchor = chunk->next; + free(chunk); + } +} + + + +void Curl_bufcp_init(struct bufc_pool *pool, + size_t chunk_size, size_t spare_max) +{ + DEBUGASSERT(chunk_size > 0); + DEBUGASSERT(spare_max > 0); + memset(pool, 0, sizeof(*pool)); + pool->chunk_size = chunk_size; + pool->spare_max = spare_max; +} + +static CURLcode bufcp_take(struct bufc_pool *pool, + struct buf_chunk **pchunk) +{ + struct buf_chunk *chunk = NULL; + + if(pool->spare) { + chunk = pool->spare; + pool->spare = chunk->next; + --pool->spare_count; + chunk_reset(chunk); + *pchunk = chunk; + return CURLE_OK; + } + + chunk = calloc(1, sizeof(*chunk) + pool->chunk_size); + if(!chunk) { + *pchunk = NULL; + return CURLE_OUT_OF_MEMORY; + } + chunk->dlen = pool->chunk_size; + *pchunk = chunk; + return CURLE_OK; +} + +static void bufcp_put(struct bufc_pool *pool, + struct buf_chunk *chunk) +{ + if(pool->spare_count >= pool->spare_max) { + free(chunk); + } + else { + chunk_reset(chunk); + chunk->next = pool->spare; + pool->spare = chunk; + ++pool->spare_count; + } +} + +void Curl_bufcp_free(struct bufc_pool *pool) +{ + chunk_list_free(&pool->spare); + pool->spare_count = 0; +} + +static void bufq_init(struct bufq *q, struct bufc_pool *pool, + size_t chunk_size, size_t max_chunks, int opts) +{ + DEBUGASSERT(chunk_size > 0); + DEBUGASSERT(max_chunks > 0); + memset(q, 0, sizeof(*q)); + q->chunk_size = chunk_size; + q->max_chunks = max_chunks; + q->pool = pool; + q->opts = opts; +} + +void Curl_bufq_init2(struct bufq *q, size_t chunk_size, size_t max_chunks, + int opts) +{ + bufq_init(q, NULL, chunk_size, max_chunks, opts); +} + +void Curl_bufq_init(struct bufq *q, size_t chunk_size, size_t max_chunks) +{ + bufq_init(q, NULL, chunk_size, max_chunks, BUFQ_OPT_NONE); +} + +void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool, + size_t max_chunks, int opts) +{ + bufq_init(q, pool, pool->chunk_size, max_chunks, opts); +} + +void Curl_bufq_free(struct bufq *q) +{ + chunk_list_free(&q->head); + chunk_list_free(&q->spare); + q->tail = NULL; + q->chunk_count = 0; +} + +void Curl_bufq_reset(struct bufq *q) +{ + struct buf_chunk *chunk; + while(q->head) { + chunk = q->head; + q->head = chunk->next; + chunk->next = q->spare; + q->spare = chunk; + } + q->tail = NULL; +} + +size_t Curl_bufq_len(const struct bufq *q) +{ + const struct buf_chunk *chunk = q->head; + size_t len = 0; + while(chunk) { + len += chunk_len(chunk); + chunk = chunk->next; + } + return len; +} + +size_t Curl_bufq_space(const struct bufq *q) +{ + size_t space = 0; + if(q->tail) + space += chunk_space(q->tail); + if(q->spare) { + struct buf_chunk *chunk = q->spare; + while(chunk) { + space += chunk->dlen; + chunk = chunk->next; + } + } + if(q->chunk_count < q->max_chunks) { + space += (q->max_chunks - q->chunk_count) * q->chunk_size; + } + return space; +} + +bool Curl_bufq_is_empty(const struct bufq *q) +{ + return !q->head || chunk_is_empty(q->head); +} + +bool Curl_bufq_is_full(const struct bufq *q) +{ + if(!q->tail || q->spare) + return FALSE; + if(q->chunk_count < q->max_chunks) + return FALSE; + if(q->chunk_count > q->max_chunks) + return TRUE; + /* we have no spares and cannot make more, is the tail full? */ + return chunk_is_full(q->tail); +} + +static struct buf_chunk *get_spare(struct bufq *q) +{ + struct buf_chunk *chunk = NULL; + + if(q->spare) { + chunk = q->spare; + q->spare = chunk->next; + chunk_reset(chunk); + return chunk; + } + + if(q->chunk_count >= q->max_chunks && (!(q->opts & BUFQ_OPT_SOFT_LIMIT))) + return NULL; + + if(q->pool) { + if(bufcp_take(q->pool, &chunk)) + return NULL; + ++q->chunk_count; + return chunk; + } + else { + chunk = calloc(1, sizeof(*chunk) + q->chunk_size); + if(!chunk) + return NULL; + chunk->dlen = q->chunk_size; + ++q->chunk_count; + return chunk; + } +} + +static void prune_head(struct bufq *q) +{ + struct buf_chunk *chunk; + + while(q->head && chunk_is_empty(q->head)) { + chunk = q->head; + q->head = chunk->next; + if(q->tail == chunk) + q->tail = q->head; + if(q->pool) { + bufcp_put(q->pool, chunk); + --q->chunk_count; + } + else if((q->chunk_count > q->max_chunks) || + (q->opts & BUFQ_OPT_NO_SPARES)) { + /* SOFT_LIMIT allowed us more than max. free spares until + * we are at max again. Or free them if we are configured + * to not use spares. */ + free(chunk); + --q->chunk_count; + } + else { + chunk->next = q->spare; + q->spare = chunk; + } + } +} + +static struct buf_chunk *get_non_full_tail(struct bufq *q) +{ + struct buf_chunk *chunk; + + if(q->tail && !chunk_is_full(q->tail)) + return q->tail; + chunk = get_spare(q); + if(chunk) { + /* new tail, and possibly new head */ + if(q->tail) { + q->tail->next = chunk; + q->tail = chunk; + } + else { + DEBUGASSERT(!q->head); + q->head = q->tail = chunk; + } + } + return chunk; +} + +ssize_t Curl_bufq_write(struct bufq *q, + const unsigned char *buf, size_t len, + CURLcode *err) +{ + struct buf_chunk *tail; + ssize_t nwritten = 0; + size_t n; + + DEBUGASSERT(q->max_chunks > 0); + while(len) { + tail = get_non_full_tail(q); + if(!tail) { + if(q->chunk_count < q->max_chunks) { + *err = CURLE_OUT_OF_MEMORY; + return -1; + } + break; + } + n = chunk_append(tail, buf, len); + if(!n) + break; + nwritten += n; + buf += n; + len -= n; + } + if(nwritten == 0 && len) { + *err = CURLE_AGAIN; + return -1; + } + *err = CURLE_OK; + return nwritten; +} + +ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len, + CURLcode *err) +{ + ssize_t nread = 0; + size_t n; + + *err = CURLE_OK; + while(len && q->head) { + n = chunk_read(q->head, buf, len); + if(n) { + nread += n; + buf += n; + len -= n; + } + prune_head(q); + } + if(nread == 0) { + *err = CURLE_AGAIN; + return -1; + } + return nread; +} + +bool Curl_bufq_peek(struct bufq *q, + const unsigned char **pbuf, size_t *plen) +{ + if(q->head && chunk_is_empty(q->head)) { + prune_head(q); + } + if(q->head && !chunk_is_empty(q->head)) { + chunk_peek(q->head, pbuf, plen); + return TRUE; + } + *pbuf = NULL; + *plen = 0; + return FALSE; +} + +bool Curl_bufq_peek_at(struct bufq *q, size_t offset, + const unsigned char **pbuf, size_t *plen) +{ + struct buf_chunk *c = q->head; + size_t clen; + + while(c) { + clen = chunk_len(c); + if(!clen) + break; + if(offset >= clen) { + offset -= clen; + c = c->next; + continue; + } + chunk_peek_at(c, offset, pbuf, plen); + return TRUE; + } + *pbuf = NULL; + *plen = 0; + return FALSE; +} + +void Curl_bufq_skip(struct bufq *q, size_t amount) +{ + size_t n; + + while(amount && q->head) { + n = chunk_skip(q->head, amount); + amount -= n; + prune_head(q); + } +} + +ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer, + void *writer_ctx, CURLcode *err) +{ + const unsigned char *buf; + size_t blen; + ssize_t nwritten = 0; + + while(Curl_bufq_peek(q, &buf, &blen)) { + ssize_t chunk_written; + + chunk_written = writer(writer_ctx, buf, blen, err); + if(chunk_written < 0) { + if(!nwritten || *err != CURLE_AGAIN) { + /* blocked on first write or real error, fail */ + nwritten = -1; + } + break; + } + if(!chunk_written) { + if(!nwritten) { + /* treat as blocked */ + *err = CURLE_AGAIN; + nwritten = -1; + } + break; + } + Curl_bufq_skip(q, (size_t)chunk_written); + nwritten += chunk_written; + } + return nwritten; +} + +ssize_t Curl_bufq_write_pass(struct bufq *q, + const unsigned char *buf, size_t len, + Curl_bufq_writer *writer, void *writer_ctx, + CURLcode *err) +{ + ssize_t nwritten = 0, n; + + *err = CURLE_OK; + while(len) { + if(Curl_bufq_is_full(q)) { + /* try to make room in case we are full */ + n = Curl_bufq_pass(q, writer, writer_ctx, err); + if(n < 0) { + if(*err != CURLE_AGAIN) { + /* real error, fail */ + return -1; + } + /* would block, bufq is full, give up */ + break; + } + } + + /* Add whatever is remaining now to bufq */ + n = Curl_bufq_write(q, buf, len, err); + if(n < 0) { + if(*err != CURLE_AGAIN) { + /* real error, fail */ + return -1; + } + /* no room in bufq */ + break; + } + /* edge case of writer returning 0 (and len is >0) + * break or we might enter an infinite loop here */ + if(n == 0) + break; + + /* Maybe only part of `data` has been added, continue to loop */ + buf += (size_t)n; + len -= (size_t)n; + nwritten += (size_t)n; + } + + if(!nwritten && len) { + *err = CURLE_AGAIN; + return -1; + } + *err = CURLE_OK; + return nwritten; +} + +ssize_t Curl_bufq_sipn(struct bufq *q, size_t max_len, + Curl_bufq_reader *reader, void *reader_ctx, + CURLcode *err) +{ + struct buf_chunk *tail = NULL; + ssize_t nread; + + *err = CURLE_AGAIN; + tail = get_non_full_tail(q); + if(!tail) { + if(q->chunk_count < q->max_chunks) { + *err = CURLE_OUT_OF_MEMORY; + return -1; + } + /* full, blocked */ + *err = CURLE_AGAIN; + return -1; + } + + nread = chunk_slurpn(tail, max_len, reader, reader_ctx, err); + if(nread < 0) { + return -1; + } + else if(nread == 0) { + /* eof */ + *err = CURLE_OK; + } + return nread; +} + +/** + * Read up to `max_len` bytes and append it to the end of the buffer queue. + * if `max_len` is 0, no limit is imposed and the call behaves exactly + * the same as `Curl_bufq_slurp()`. + * Returns the total amount of buf read (may be 0) or -1 on other + * reader errors. + * Note that even in case of a -1 chunks may have been read and + * the buffer queue will have different length than before. + */ +static ssize_t bufq_slurpn(struct bufq *q, size_t max_len, + Curl_bufq_reader *reader, void *reader_ctx, + CURLcode *err) +{ + ssize_t nread = 0, n; + + *err = CURLE_AGAIN; + while(1) { + + n = Curl_bufq_sipn(q, max_len, reader, reader_ctx, err); + if(n < 0) { + if(!nread || *err != CURLE_AGAIN) { + /* blocked on first read or real error, fail */ + nread = -1; + } + else + *err = CURLE_OK; + break; + } + else if(n == 0) { + /* eof */ + *err = CURLE_OK; + break; + } + nread += (size_t)n; + if(max_len) { + DEBUGASSERT((size_t)n <= max_len); + max_len -= (size_t)n; + if(!max_len) + break; + } + /* give up slurping when we get less bytes than we asked for */ + if(q->tail && !chunk_is_full(q->tail)) + break; + } + return nread; +} + +ssize_t Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader, + void *reader_ctx, CURLcode *err) +{ + return bufq_slurpn(q, 0, reader, reader_ctx, err); +} diff --git a/Utilities/cmcurl/lib/bufq.h b/Utilities/cmcurl/lib/bufq.h new file mode 100644 index 0000000..089d61b --- /dev/null +++ b/Utilities/cmcurl/lib/bufq.h @@ -0,0 +1,265 @@ +#ifndef HEADER_CURL_BUFQ_H +#define HEADER_CURL_BUFQ_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#include <curl/curl.h> + +/** + * A chunk of bytes for reading and writing. + * The size is fixed a creation with read and write offset + * for where unread content is. + */ +struct buf_chunk { + struct buf_chunk *next; /* to keep it in a list */ + size_t dlen; /* the amount of allocated x.data[] */ + size_t r_offset; /* first unread bytes */ + size_t w_offset; /* one after last written byte */ + union { + unsigned char data[1]; /* the buffer for `dlen` bytes */ + void *dummy; /* alignment */ + } x; +}; + +/** + * A pool for providing/keeping a number of chunks of the same size + * + * The same pool can be shared by many `bufq` instances. However, a pool + * is not thread safe. All bufqs using it are supposed to operate in the + * same thread. + */ +struct bufc_pool { + struct buf_chunk *spare; /* list of available spare chunks */ + size_t chunk_size; /* the size of chunks in this pool */ + size_t spare_count; /* current number of spare chunks in list */ + size_t spare_max; /* max number of spares to keep */ +}; + +void Curl_bufcp_init(struct bufc_pool *pool, + size_t chunk_size, size_t spare_max); + +void Curl_bufcp_free(struct bufc_pool *pool); + +/** + * A queue of byte chunks for reading and writing. + * Reading is done from `head`, writing is done to `tail`. + * + * `bufq`s can be empty or full or neither. Its `len` is the number + * of bytes that can be read. For an empty bufq, `len` will be 0. + * + * By default, a bufq can hold up to `max_chunks * chunk_size` number + * of bytes. When `max_chunks` are used (in the `head` list) and the + * `tail` chunk is full, the bufq will report that it is full. + * + * On a full bufq, `len` may be less than the maximum number of bytes, + * e.g. when the head chunk is partially read. `len` may also become + * larger than the max when option `BUFQ_OPT_SOFT_LIMIT` is used. + * + * By default, writing to a full bufq will return (-1, CURLE_AGAIN). Same + * as reading from an empty bufq. + * With `BUFQ_OPT_SOFT_LIMIT` set, a bufq will allow writing becond this + * limit and use more than `max_chunks`. However it will report that it + * is full nevertheless. This is provided for situation where writes + * preferably never fail (except for memory exhaustion). + * + * By default and without a pool, a bufq will keep chunks that read + * read empty in its `spare` list. Option `BUFQ_OPT_NO_SPARES` will + * disable that and free chunks once they become empty. + * + * When providing a pool to a bufq, all chunk creation and spare handling + * will be delegated to that pool. + */ +struct bufq { + struct buf_chunk *head; /* chunk with bytes to read from */ + struct buf_chunk *tail; /* chunk to write to */ + struct buf_chunk *spare; /* list of free chunks, unless `pool` */ + struct bufc_pool *pool; /* optional pool for free chunks */ + size_t chunk_count; /* current number of chunks in `head+spare` */ + size_t max_chunks; /* max `head` chunks to use */ + size_t chunk_size; /* size of chunks to manage */ + int opts; /* options for handling queue, see below */ +}; + +/** + * Default behaviour: chunk limit is "hard", meaning attempts to write + * more bytes than can be hold in `max_chunks` is refused and will return + * -1, CURLE_AGAIN. */ +#define BUFQ_OPT_NONE (0) +/** + * Make `max_chunks` a "soft" limit. A bufq will report that it is "full" + * when `max_chunks` are used, but allows writing beyond this limit. + */ +#define BUFQ_OPT_SOFT_LIMIT (1 << 0) +/** + * Do not keep spare chunks. + */ +#define BUFQ_OPT_NO_SPARES (1 << 1) + +/** + * Initialize a buffer queue that can hold up to `max_chunks` buffers + * each of size `chunk_size`. The bufq will not allow writing of + * more bytes than can be held in `max_chunks`. + */ +void Curl_bufq_init(struct bufq *q, size_t chunk_size, size_t max_chunks); + +/** + * Initialize a buffer queue that can hold up to `max_chunks` buffers + * each of size `chunk_size` with the given options. See `BUFQ_OPT_*`. + */ +void Curl_bufq_init2(struct bufq *q, size_t chunk_size, + size_t max_chunks, int opts); + +void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool, + size_t max_chunks, int opts); + +/** + * Reset the buffer queue to be empty. Will keep any allocated buffer + * chunks around. + */ +void Curl_bufq_reset(struct bufq *q); + +/** + * Free all resources held by the buffer queue. + */ +void Curl_bufq_free(struct bufq *q); + +/** + * Return the total amount of data in the queue. + */ +size_t Curl_bufq_len(const struct bufq *q); + +/** + * Return the total amount of free space in the queue. + * The returned length is the number of bytes that can + * be expected to be written successfully to the bufq, + * providing no memory allocations fail. + */ +size_t Curl_bufq_space(const struct bufq *q); + +/** + * Returns TRUE iff there is no data in the buffer queue. + */ +bool Curl_bufq_is_empty(const struct bufq *q); + +/** + * Returns TRUE iff there is no space left in the buffer queue. + */ +bool Curl_bufq_is_full(const struct bufq *q); + +/** + * Write buf to the end of the buffer queue. The buf is copied + * and the amount of copied bytes is returned. + * A return code of -1 indicates an error, setting `err` to the + * cause. An err of CURLE_AGAIN is returned if the buffer queue is full. + */ +ssize_t Curl_bufq_write(struct bufq *q, + const unsigned char *buf, size_t len, + CURLcode *err); + +/** + * Read buf from the start of the buffer queue. The buf is copied + * and the amount of copied bytes is returned. + * A return code of -1 indicates an error, setting `err` to the + * cause. An err of CURLE_AGAIN is returned if the buffer queue is empty. + */ +ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len, + CURLcode *err); + +/** + * Peek at the head chunk in the buffer queue. Returns a pointer to + * the chunk buf (at the current offset) and its length. Does not + * modify the buffer queue. + * Returns TRUE iff bytes are available. Sets `pbuf` to NULL and `plen` + * to 0 when no bytes are available. + * Repeated calls return the same information until the buffer queue + * is modified, see `Curl_bufq_skip()`` + */ +bool Curl_bufq_peek(struct bufq *q, + const unsigned char **pbuf, size_t *plen); + +bool Curl_bufq_peek_at(struct bufq *q, size_t offset, + const unsigned char **pbuf, size_t *plen); + +/** + * Tell the buffer queue to discard `amount` buf bytes at the head + * of the queue. Skipping more buf than is currently buffered will + * just empty the queue. + */ +void Curl_bufq_skip(struct bufq *q, size_t amount); + +typedef ssize_t Curl_bufq_writer(void *writer_ctx, + const unsigned char *buf, size_t len, + CURLcode *err); +/** + * Passes the chunks in the buffer queue to the writer and returns + * the amount of buf written. A writer may return -1 and CURLE_AGAIN + * to indicate blocking at which point the queue will stop and return + * the amount of buf passed so far. + * -1 is returned on any other errors reported by the writer. + * Note that in case of a -1 chunks may have been written and + * the buffer queue will have different length than before. + */ +ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer, + void *writer_ctx, CURLcode *err); + +typedef ssize_t Curl_bufq_reader(void *reader_ctx, + unsigned char *buf, size_t len, + CURLcode *err); + +/** + * Read date and append it to the end of the buffer queue until the + * reader returns blocking or the queue is full. A reader returns + * -1 and CURLE_AGAIN to indicate blocking. + * Returns the total amount of buf read (may be 0) or -1 on other + * reader errors. + * Note that in case of a -1 chunks may have been read and + * the buffer queue will have different length than before. + */ +ssize_t Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader, + void *reader_ctx, CURLcode *err); + +/** + * Read *once* up to `max_len` bytes and append it to the buffer. + * if `max_len` is 0, no limit is imposed besides the chunk space. + * Returns the total amount of buf read (may be 0) or -1 on other + * reader errors. + */ +ssize_t Curl_bufq_sipn(struct bufq *q, size_t max_len, + Curl_bufq_reader *reader, void *reader_ctx, + CURLcode *err); + +/** + * Write buf to the end of the buffer queue. + * Will write bufq content or passed `buf` directly using the `writer` + * callback when it sees fit. 'buf' might get passed directly + * on or is placed into the buffer, depending on `len` and current + * amount buffered, chunk size, etc. + */ +ssize_t Curl_bufq_write_pass(struct bufq *q, + const unsigned char *buf, size_t len, + Curl_bufq_writer *writer, void *writer_ctx, + CURLcode *err); + +#endif /* HEADER_CURL_BUFQ_H */ diff --git a/Utilities/cmcurl/lib/bufref.c b/Utilities/cmcurl/lib/bufref.c new file mode 100644 index 0000000..ce686b6 --- /dev/null +++ b/Utilities/cmcurl/lib/bufref.c @@ -0,0 +1,129 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "urldata.h" +#include "bufref.h" + +#include "curl_memory.h" +#include "memdebug.h" + +#define SIGNATURE 0x5c48e9b2 /* Random pattern. */ + +/* + * Init a bufref struct. + */ +void Curl_bufref_init(struct bufref *br) +{ + DEBUGASSERT(br); + br->dtor = NULL; + br->ptr = NULL; + br->len = 0; + +#ifdef DEBUGBUILD + br->signature = SIGNATURE; +#endif +} + +/* + * Free the buffer and re-init the necessary fields. It doesn't touch the + * 'signature' field and thus this buffer reference can be reused. + */ + +void Curl_bufref_free(struct bufref *br) +{ + DEBUGASSERT(br); + DEBUGASSERT(br->signature == SIGNATURE); + DEBUGASSERT(br->ptr || !br->len); + + if(br->ptr && br->dtor) + br->dtor((void *) br->ptr); + + br->dtor = NULL; + br->ptr = NULL; + br->len = 0; +} + +/* + * Set the buffer reference to new values. The previously referenced buffer + * is released before assignment. + */ +void Curl_bufref_set(struct bufref *br, const void *ptr, size_t len, + void (*dtor)(void *)) +{ + DEBUGASSERT(ptr || !len); + DEBUGASSERT(len <= CURL_MAX_INPUT_LENGTH); + + Curl_bufref_free(br); + br->ptr = (const unsigned char *) ptr; + br->len = len; + br->dtor = dtor; +} + +/* + * Get a pointer to the referenced buffer. + */ +const unsigned char *Curl_bufref_ptr(const struct bufref *br) +{ + DEBUGASSERT(br); + DEBUGASSERT(br->signature == SIGNATURE); + DEBUGASSERT(br->ptr || !br->len); + + return br->ptr; +} + +/* + * Get the length of the referenced buffer data. + */ +size_t Curl_bufref_len(const struct bufref *br) +{ + DEBUGASSERT(br); + DEBUGASSERT(br->signature == SIGNATURE); + DEBUGASSERT(br->ptr || !br->len); + + return br->len; +} + +CURLcode Curl_bufref_memdup(struct bufref *br, const void *ptr, size_t len) +{ + unsigned char *cpy = NULL; + + DEBUGASSERT(br); + DEBUGASSERT(br->signature == SIGNATURE); + DEBUGASSERT(br->ptr || !br->len); + DEBUGASSERT(ptr || !len); + DEBUGASSERT(len <= CURL_MAX_INPUT_LENGTH); + + if(ptr) { + cpy = malloc(len + 1); + if(!cpy) + return CURLE_OUT_OF_MEMORY; + if(len) + memcpy(cpy, ptr, len); + cpy[len] = '\0'; + } + + Curl_bufref_set(br, cpy, len, curl_free); + return CURLE_OK; +} diff --git a/Utilities/cmcurl/lib/bufref.h b/Utilities/cmcurl/lib/bufref.h new file mode 100644 index 0000000..dd424f1 --- /dev/null +++ b/Utilities/cmcurl/lib/bufref.h @@ -0,0 +1,48 @@ +#ifndef HEADER_CURL_BUFREF_H +#define HEADER_CURL_BUFREF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Generic buffer reference. + */ +struct bufref { + void (*dtor)(void *); /* Associated destructor. */ + const unsigned char *ptr; /* Referenced data buffer. */ + size_t len; /* The data size in bytes. */ +#ifdef DEBUGBUILD + int signature; /* Detect API use mistakes. */ +#endif +}; + + +void Curl_bufref_init(struct bufref *br); +void Curl_bufref_set(struct bufref *br, const void *ptr, size_t len, + void (*dtor)(void *)); +const unsigned char *Curl_bufref_ptr(const struct bufref *br); +size_t Curl_bufref_len(const struct bufref *br); +CURLcode Curl_bufref_memdup(struct bufref *br, const void *ptr, size_t len); +void Curl_bufref_free(struct bufref *br); + +#endif diff --git a/Utilities/cmcurl/lib/c-hyper.c b/Utilities/cmcurl/lib/c-hyper.c new file mode 100644 index 0000000..787d6bb --- /dev/null +++ b/Utilities/cmcurl/lib/c-hyper.c @@ -0,0 +1,1247 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.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 + * + ***************************************************************************/ + +/* Curl's integration with Hyper. This replaces certain functions in http.c, + * based on configuration #defines. This implementation supports HTTP/1.1 but + * not HTTP/2. + */ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_HYPER) + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#include <hyper.h> +#include "urldata.h" +#include "sendf.h" +#include "transfer.h" +#include "multiif.h" +#include "progress.h" +#include "content_encoding.h" +#include "ws.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +typedef enum { + USERDATA_NOT_SET = 0, /* for tasks with no userdata set; must be zero */ + USERDATA_RESP_BODY +} userdata_t; + +size_t Curl_hyper_recv(void *userp, hyper_context *ctx, + uint8_t *buf, size_t buflen) +{ + struct Curl_easy *data = userp; + struct connectdata *conn = data->conn; + CURLcode result; + ssize_t nread; + DEBUGASSERT(conn); + (void)ctx; + + DEBUGF(infof(data, "Curl_hyper_recv(%zu)", buflen)); + result = Curl_read(data, conn->sockfd, (char *)buf, buflen, &nread); + if(result == CURLE_AGAIN) { + /* would block, register interest */ + DEBUGF(infof(data, "Curl_hyper_recv(%zu) -> EAGAIN", buflen)); + if(data->hyp.read_waker) + hyper_waker_free(data->hyp.read_waker); + data->hyp.read_waker = hyper_context_waker(ctx); + if(!data->hyp.read_waker) { + failf(data, "Couldn't make the read hyper_context_waker"); + return HYPER_IO_ERROR; + } + return HYPER_IO_PENDING; + } + else if(result) { + failf(data, "Curl_read failed"); + return HYPER_IO_ERROR; + } + DEBUGF(infof(data, "Curl_hyper_recv(%zu) -> %zd", buflen, nread)); + return (size_t)nread; +} + +size_t Curl_hyper_send(void *userp, hyper_context *ctx, + const uint8_t *buf, size_t buflen) +{ + struct Curl_easy *data = userp; + struct connectdata *conn = data->conn; + CURLcode result; + ssize_t nwrote; + + DEBUGF(infof(data, "Curl_hyper_send(%zu)", buflen)); + result = Curl_write(data, conn->sockfd, (void *)buf, buflen, &nwrote); + if(!result && !nwrote) + result = CURLE_AGAIN; + if(result == CURLE_AGAIN) { + DEBUGF(infof(data, "Curl_hyper_send(%zu) -> EAGAIN", buflen)); + /* would block, register interest */ + if(data->hyp.write_waker) + hyper_waker_free(data->hyp.write_waker); + data->hyp.write_waker = hyper_context_waker(ctx); + if(!data->hyp.write_waker) { + failf(data, "Couldn't make the write hyper_context_waker"); + return HYPER_IO_ERROR; + } + return HYPER_IO_PENDING; + } + else if(result) { + failf(data, "Curl_write failed"); + return HYPER_IO_ERROR; + } + DEBUGF(infof(data, "Curl_hyper_send(%zu) -> %zd", buflen, nwrote)); + return (size_t)nwrote; +} + +static int hyper_each_header(void *userdata, + const uint8_t *name, + size_t name_len, + const uint8_t *value, + size_t value_len) +{ + struct Curl_easy *data = (struct Curl_easy *)userdata; + size_t len; + char *headp; + CURLcode result; + int writetype; + + if(name_len + value_len + 2 > CURL_MAX_HTTP_HEADER) { + failf(data, "Too long response header"); + data->state.hresult = CURLE_OUT_OF_MEMORY; + return HYPER_ITER_BREAK; + } + + if(!data->req.bytecount) + Curl_pgrsTime(data, TIMER_STARTTRANSFER); + + Curl_dyn_reset(&data->state.headerb); + if(name_len) { + if(Curl_dyn_addf(&data->state.headerb, "%.*s: %.*s\r\n", + (int) name_len, name, (int) value_len, value)) + return HYPER_ITER_BREAK; + } + else { + if(Curl_dyn_addn(&data->state.headerb, STRCONST("\r\n"))) + return HYPER_ITER_BREAK; + } + len = Curl_dyn_len(&data->state.headerb); + headp = Curl_dyn_ptr(&data->state.headerb); + + result = Curl_http_header(data, data->conn, headp); + if(result) { + data->state.hresult = result; + return HYPER_ITER_BREAK; + } + + Curl_debug(data, CURLINFO_HEADER_IN, headp, len); + + writetype = CLIENTWRITE_HEADER; + if(data->state.hconnect) + writetype |= CLIENTWRITE_CONNECT; + if(data->req.httpcode/100 == 1) + writetype |= CLIENTWRITE_1XX; + result = Curl_client_write(data, writetype, headp, len); + if(result) { + data->state.hresult = CURLE_ABORTED_BY_CALLBACK; + return HYPER_ITER_BREAK; + } + + result = Curl_bump_headersize(data, len, FALSE); + if(result) { + data->state.hresult = result; + return HYPER_ITER_BREAK; + } + return HYPER_ITER_CONTINUE; +} + +static int hyper_body_chunk(void *userdata, const hyper_buf *chunk) +{ + char *buf = (char *)hyper_buf_bytes(chunk); + size_t len = hyper_buf_len(chunk); + struct Curl_easy *data = (struct Curl_easy *)userdata; + struct SingleRequest *k = &data->req; + CURLcode result = CURLE_OK; + + if(0 == k->bodywrites) { + bool done = FALSE; +#if defined(USE_NTLM) + struct connectdata *conn = data->conn; + if(conn->bits.close && + (((data->req.httpcode == 401) && + (conn->http_ntlm_state == NTLMSTATE_TYPE2)) || + ((data->req.httpcode == 407) && + (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) { + infof(data, "Connection closed while negotiating NTLM"); + data->state.authproblem = TRUE; + Curl_safefree(data->req.newurl); + } +#endif + if(data->state.expect100header) { + Curl_expire_done(data, EXPIRE_100_TIMEOUT); + if(data->req.httpcode < 400) { + k->exp100 = EXP100_SEND_DATA; + if(data->hyp.exp100_waker) { + hyper_waker_wake(data->hyp.exp100_waker); + data->hyp.exp100_waker = NULL; + } + } + else { /* >= 4xx */ + k->exp100 = EXP100_FAILED; + } + } + if(data->state.hconnect && (data->req.httpcode/100 != 2) && + data->state.authproxy.done) { + done = TRUE; + result = CURLE_OK; + } + else + result = Curl_http_firstwrite(data, data->conn, &done); + if(result || done) { + infof(data, "Return early from hyper_body_chunk"); + data->state.hresult = result; + return HYPER_ITER_BREAK; + } + } + result = Curl_client_write(data, CLIENTWRITE_BODY, buf, len); + + if(result) { + data->state.hresult = result; + return HYPER_ITER_BREAK; + } + + return HYPER_ITER_CONTINUE; +} + +/* + * Hyper does not consider the status line, the first line in an HTTP/1 + * response, to be a header. The libcurl API does. This function sends the + * status line in the header callback. */ +static CURLcode status_line(struct Curl_easy *data, + struct connectdata *conn, + uint16_t http_status, + int http_version, + const uint8_t *reason, size_t rlen) +{ + CURLcode result; + size_t len; + const char *vstr; + int writetype; + vstr = http_version == HYPER_HTTP_VERSION_1_1 ? "1.1" : + (http_version == HYPER_HTTP_VERSION_2 ? "2" : "1.0"); + + /* We need to set 'httpcodeq' for functions that check the response code in + a single place. */ + data->req.httpcode = http_status; + + if(data->state.hconnect) + /* CONNECT */ + data->info.httpproxycode = http_status; + else { + conn->httpversion = + http_version == HYPER_HTTP_VERSION_1_1 ? 11 : + (http_version == HYPER_HTTP_VERSION_2 ? 20 : 10); + if(http_version == HYPER_HTTP_VERSION_1_0) + data->state.httpwant = CURL_HTTP_VERSION_1_0; + + result = Curl_http_statusline(data, conn); + if(result) + return result; + } + + Curl_dyn_reset(&data->state.headerb); + + result = Curl_dyn_addf(&data->state.headerb, "HTTP/%s %03d %.*s\r\n", + vstr, + (int)http_status, + (int)rlen, reason); + if(result) + return result; + len = Curl_dyn_len(&data->state.headerb); + Curl_debug(data, CURLINFO_HEADER_IN, Curl_dyn_ptr(&data->state.headerb), + len); + + writetype = CLIENTWRITE_HEADER|CLIENTWRITE_STATUS; + if(data->state.hconnect) + writetype |= CLIENTWRITE_CONNECT; + result = Curl_client_write(data, writetype, + Curl_dyn_ptr(&data->state.headerb), len); + if(result) + return result; + + result = Curl_bump_headersize(data, len, FALSE); + return result; +} + +/* + * Hyper does not pass on the last empty response header. The libcurl API + * does. This function sends an empty header in the header callback. + */ +static CURLcode empty_header(struct Curl_easy *data) +{ + CURLcode result = Curl_http_size(data); + if(!result) { + result = hyper_each_header(data, NULL, 0, NULL, 0) ? + CURLE_WRITE_ERROR : CURLE_OK; + if(result) + failf(data, "hyperstream: couldn't pass blank header"); + } + return result; +} + +CURLcode Curl_hyper_stream(struct Curl_easy *data, + struct connectdata *conn, + int *didwhat, + bool *done, + int select_res) +{ + hyper_response *resp = NULL; + uint16_t http_status; + int http_version; + hyper_headers *headers = NULL; + hyper_body *resp_body = NULL; + struct hyptransfer *h = &data->hyp; + hyper_task *task; + hyper_task *foreach; + const uint8_t *reasonp; + size_t reason_len; + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + (void)conn; + + if(k->exp100 > EXP100_SEND_DATA) { + struct curltime now = Curl_now(); + timediff_t ms = Curl_timediff(now, k->start100); + if(ms >= data->set.expect_100_timeout) { + /* we've waited long enough, continue anyway */ + k->exp100 = EXP100_SEND_DATA; + k->keepon |= KEEP_SEND; + Curl_expire_done(data, EXPIRE_100_TIMEOUT); + infof(data, "Done waiting for 100-continue"); + if(data->hyp.exp100_waker) { + hyper_waker_wake(data->hyp.exp100_waker); + data->hyp.exp100_waker = NULL; + } + } + } + + if(select_res & CURL_CSELECT_IN) { + if(h->read_waker) + hyper_waker_wake(h->read_waker); + h->read_waker = NULL; + } + if(select_res & CURL_CSELECT_OUT) { + if(h->write_waker) + hyper_waker_wake(h->write_waker); + h->write_waker = NULL; + } + + *done = FALSE; + do { + hyper_task_return_type t; + task = hyper_executor_poll(h->exec); + if(!task) { + *didwhat = KEEP_RECV; + break; + } + t = hyper_task_type(task); + if(t == HYPER_TASK_ERROR) { + hyper_error *hypererr = hyper_task_value(task); + hyper_task_free(task); + if(data->state.hresult) { + /* override Hyper's view, might not even be an error */ + result = data->state.hresult; + infof(data, "hyperstream is done (by early callback)"); + } + else { + uint8_t errbuf[256]; + size_t errlen = hyper_error_print(hypererr, errbuf, sizeof(errbuf)); + hyper_code code = hyper_error_code(hypererr); + failf(data, "Hyper: [%d] %.*s", (int)code, (int)errlen, errbuf); + switch(code) { + case HYPERE_ABORTED_BY_CALLBACK: + result = CURLE_OK; + break; + case HYPERE_UNEXPECTED_EOF: + if(!data->req.bytecount) + result = CURLE_GOT_NOTHING; + else + result = CURLE_RECV_ERROR; + break; + case HYPERE_INVALID_PEER_MESSAGE: + /* bump headerbytecount to avoid the count remaining at zero and + appearing to not having read anything from the peer at all */ + data->req.headerbytecount++; + result = CURLE_UNSUPPORTED_PROTOCOL; /* maybe */ + break; + default: + result = CURLE_RECV_ERROR; + break; + } + } + *done = TRUE; + hyper_error_free(hypererr); + break; + } + else if(t == HYPER_TASK_EMPTY) { + void *userdata = hyper_task_userdata(task); + hyper_task_free(task); + if((userdata_t)userdata == USERDATA_RESP_BODY) { + /* end of transfer */ + *done = TRUE; + infof(data, "hyperstream is done"); + if(!k->bodywrites) { + /* hyper doesn't always call the body write callback */ + bool stilldone; + result = Curl_http_firstwrite(data, data->conn, &stilldone); + } + break; + } + else { + /* A background task for hyper; ignore */ + continue; + } + } + + DEBUGASSERT(HYPER_TASK_RESPONSE); + + resp = hyper_task_value(task); + hyper_task_free(task); + + *didwhat = KEEP_RECV; + if(!resp) { + failf(data, "hyperstream: couldn't get response"); + return CURLE_RECV_ERROR; + } + + http_status = hyper_response_status(resp); + http_version = hyper_response_version(resp); + reasonp = hyper_response_reason_phrase(resp); + reason_len = hyper_response_reason_phrase_len(resp); + + if(http_status == 417 && data->state.expect100header) { + infof(data, "Got 417 while waiting for a 100"); + data->state.disableexpect = TRUE; + data->req.newurl = strdup(data->state.url); + Curl_done_sending(data, k); + } + + result = status_line(data, conn, + http_status, http_version, reasonp, reason_len); + if(result) + break; + + headers = hyper_response_headers(resp); + if(!headers) { + failf(data, "hyperstream: couldn't get response headers"); + result = CURLE_RECV_ERROR; + break; + } + + /* the headers are already received */ + hyper_headers_foreach(headers, hyper_each_header, data); + if(data->state.hresult) { + result = data->state.hresult; + break; + } + + result = empty_header(data); + if(result) + break; + + k->deductheadercount = + (100 <= http_status && 199 >= http_status)?k->headerbytecount:0; +#ifdef USE_WEBSOCKETS + if(k->upgr101 == UPGR101_WS) { + if(http_status == 101) { + /* verify the response */ + result = Curl_ws_accept(data, NULL, 0); + if(result) + return result; + } + else { + failf(data, "Expected 101, got %u", k->httpcode); + result = CURLE_HTTP_RETURNED_ERROR; + break; + } + } +#endif + + /* Curl_http_auth_act() checks what authentication methods that are + * available and decides which one (if any) to use. It will set 'newurl' + * if an auth method was picked. */ + result = Curl_http_auth_act(data); + if(result) + break; + + resp_body = hyper_response_body(resp); + if(!resp_body) { + failf(data, "hyperstream: couldn't get response body"); + result = CURLE_RECV_ERROR; + break; + } + foreach = hyper_body_foreach(resp_body, hyper_body_chunk, data); + if(!foreach) { + failf(data, "hyperstream: body foreach failed"); + result = CURLE_OUT_OF_MEMORY; + break; + } + hyper_task_set_userdata(foreach, (void *)USERDATA_RESP_BODY); + if(HYPERE_OK != hyper_executor_push(h->exec, foreach)) { + failf(data, "Couldn't hyper_executor_push the body-foreach"); + result = CURLE_OUT_OF_MEMORY; + break; + } + + hyper_response_free(resp); + resp = NULL; + } while(1); + if(resp) + hyper_response_free(resp); + return result; +} + +static CURLcode debug_request(struct Curl_easy *data, + const char *method, + const char *path) +{ + char *req = aprintf("%s %s HTTP/1.1\r\n", method, path); + if(!req) + return CURLE_OUT_OF_MEMORY; + Curl_debug(data, CURLINFO_HEADER_OUT, req, strlen(req)); + free(req); + return CURLE_OK; +} + +/* + * Given a full header line "name: value" (optional CRLF in the input, should + * be in the output), add to Hyper and send to the debug callback. + * + * Supports multiple headers. + */ + +CURLcode Curl_hyper_header(struct Curl_easy *data, hyper_headers *headers, + const char *line) +{ + const char *p; + const char *n; + size_t nlen; + const char *v; + size_t vlen; + bool newline = TRUE; + int numh = 0; + + if(!line) + return CURLE_OK; + n = line; + do { + size_t linelen = 0; + + p = strchr(n, ':'); + if(!p) + /* this is fine if we already added at least one header */ + return numh ? CURLE_OK : CURLE_BAD_FUNCTION_ARGUMENT; + nlen = p - n; + p++; /* move past the colon */ + while(*p == ' ') + p++; + v = p; + p = strchr(v, '\r'); + if(!p) { + p = strchr(v, '\n'); + if(p) + linelen = 1; /* LF only */ + else { + p = strchr(v, '\0'); + newline = FALSE; /* no newline */ + } + } + else + linelen = 2; /* CRLF ending */ + linelen += (p - n); + vlen = p - v; + + if(HYPERE_OK != hyper_headers_add(headers, (uint8_t *)n, nlen, + (uint8_t *)v, vlen)) { + failf(data, "hyper refused to add header '%s'", line); + return CURLE_OUT_OF_MEMORY; + } + if(data->set.verbose) { + char *ptr = NULL; + if(!newline) { + ptr = aprintf("%.*s\r\n", (int)linelen, line); + if(!ptr) + return CURLE_OUT_OF_MEMORY; + Curl_debug(data, CURLINFO_HEADER_OUT, ptr, linelen + 2); + free(ptr); + } + else + Curl_debug(data, CURLINFO_HEADER_OUT, (char *)n, linelen); + } + numh++; + n += linelen; + } while(newline); + return CURLE_OK; +} + +static CURLcode request_target(struct Curl_easy *data, + struct connectdata *conn, + const char *method, + hyper_request *req) +{ + CURLcode result; + struct dynbuf r; + + Curl_dyn_init(&r, DYN_HTTP_REQUEST); + + result = Curl_http_target(data, conn, &r); + if(result) + return result; + + if(hyper_request_set_uri(req, (uint8_t *)Curl_dyn_uptr(&r), + Curl_dyn_len(&r))) { + failf(data, "error setting uri to hyper"); + result = CURLE_OUT_OF_MEMORY; + } + else + result = debug_request(data, method, Curl_dyn_ptr(&r)); + + Curl_dyn_free(&r); + + return result; +} + +static int uploadpostfields(void *userdata, hyper_context *ctx, + hyper_buf **chunk) +{ + struct Curl_easy *data = (struct Curl_easy *)userdata; + (void)ctx; + if(data->req.exp100 > EXP100_SEND_DATA) { + if(data->req.exp100 == EXP100_FAILED) + return HYPER_POLL_ERROR; + + /* still waiting confirmation */ + if(data->hyp.exp100_waker) + hyper_waker_free(data->hyp.exp100_waker); + data->hyp.exp100_waker = hyper_context_waker(ctx); + return HYPER_POLL_PENDING; + } + if(data->req.upload_done) + *chunk = NULL; /* nothing more to deliver */ + else { + /* send everything off in a single go */ + hyper_buf *copy = hyper_buf_copy(data->set.postfields, + (size_t)data->req.p.http->postsize); + if(copy) + *chunk = copy; + else { + data->state.hresult = CURLE_OUT_OF_MEMORY; + return HYPER_POLL_ERROR; + } + /* increasing the writebytecount here is a little premature but we + don't know exactly when the body is sent */ + data->req.writebytecount += (size_t)data->req.p.http->postsize; + Curl_pgrsSetUploadCounter(data, data->req.writebytecount); + data->req.upload_done = TRUE; + } + return HYPER_POLL_READY; +} + +static int uploadstreamed(void *userdata, hyper_context *ctx, + hyper_buf **chunk) +{ + size_t fillcount; + struct Curl_easy *data = (struct Curl_easy *)userdata; + struct connectdata *conn = (struct connectdata *)data->conn; + CURLcode result; + (void)ctx; + + if(data->req.exp100 > EXP100_SEND_DATA) { + if(data->req.exp100 == EXP100_FAILED) + return HYPER_POLL_ERROR; + + /* still waiting confirmation */ + if(data->hyp.exp100_waker) + hyper_waker_free(data->hyp.exp100_waker); + data->hyp.exp100_waker = hyper_context_waker(ctx); + return HYPER_POLL_PENDING; + } + + if(data->req.upload_chunky && conn->bits.authneg) { + fillcount = 0; + data->req.upload_chunky = FALSE; + result = CURLE_OK; + } + else { + result = Curl_fillreadbuffer(data, data->set.upload_buffer_size, + &fillcount); + } + if(result) { + data->state.hresult = result; + return HYPER_POLL_ERROR; + } + if(!fillcount) { + if((data->req.keepon & KEEP_SEND_PAUSE) != KEEP_SEND_PAUSE) + /* done! */ + *chunk = NULL; + else { + /* paused, save a waker */ + if(data->hyp.send_body_waker) + hyper_waker_free(data->hyp.send_body_waker); + data->hyp.send_body_waker = hyper_context_waker(ctx); + return HYPER_POLL_PENDING; + } + } + else { + hyper_buf *copy = hyper_buf_copy((uint8_t *)data->state.ulbuf, fillcount); + if(copy) + *chunk = copy; + else { + data->state.hresult = CURLE_OUT_OF_MEMORY; + return HYPER_POLL_ERROR; + } + /* increasing the writebytecount here is a little premature but we + don't know exactly when the body is sent */ + data->req.writebytecount += fillcount; + Curl_pgrsSetUploadCounter(data, data->req.writebytecount); + } + return HYPER_POLL_READY; +} + +/* + * bodysend() sets up headers in the outgoing request for an HTTP transfer that + * sends a body + */ + +static CURLcode bodysend(struct Curl_easy *data, + struct connectdata *conn, + hyper_headers *headers, + hyper_request *hyperreq, + Curl_HttpReq httpreq) +{ + struct HTTP *http = data->req.p.http; + CURLcode result = CURLE_OK; + struct dynbuf req; + if((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) + Curl_pgrsSetUploadSize(data, 0); /* no request body */ + else { + hyper_body *body; + Curl_dyn_init(&req, DYN_HTTP_REQUEST); + result = Curl_http_bodysend(data, conn, &req, httpreq); + + if(!result) + result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&req)); + + Curl_dyn_free(&req); + + body = hyper_body_new(); + hyper_body_set_userdata(body, data); + if(data->set.postfields) + hyper_body_set_data_func(body, uploadpostfields); + else { + result = Curl_get_upload_buffer(data); + if(result) { + hyper_body_free(body); + return result; + } + /* init the "upload from here" pointer */ + data->req.upload_fromhere = data->state.ulbuf; + hyper_body_set_data_func(body, uploadstreamed); + } + if(HYPERE_OK != hyper_request_set_body(hyperreq, body)) { + /* fail */ + result = CURLE_OUT_OF_MEMORY; + } + } + http->sending = HTTPSEND_BODY; + return result; +} + +static CURLcode cookies(struct Curl_easy *data, + struct connectdata *conn, + hyper_headers *headers) +{ + struct dynbuf req; + CURLcode result; + Curl_dyn_init(&req, DYN_HTTP_REQUEST); + + result = Curl_http_cookies(data, conn, &req); + if(!result) + result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&req)); + Curl_dyn_free(&req); + return result; +} + +/* called on 1xx responses */ +static void http1xx_cb(void *arg, struct hyper_response *resp) +{ + struct Curl_easy *data = (struct Curl_easy *)arg; + hyper_headers *headers = NULL; + CURLcode result = CURLE_OK; + uint16_t http_status; + int http_version; + const uint8_t *reasonp; + size_t reason_len; + + infof(data, "Got HTTP 1xx informational"); + + http_status = hyper_response_status(resp); + http_version = hyper_response_version(resp); + reasonp = hyper_response_reason_phrase(resp); + reason_len = hyper_response_reason_phrase_len(resp); + + result = status_line(data, data->conn, + http_status, http_version, reasonp, reason_len); + if(!result) { + headers = hyper_response_headers(resp); + if(!headers) { + failf(data, "hyperstream: couldn't get 1xx response headers"); + result = CURLE_RECV_ERROR; + } + } + data->state.hresult = result; + + if(!result) { + /* the headers are already received */ + hyper_headers_foreach(headers, hyper_each_header, data); + /* this callback also sets data->state.hresult on error */ + + if(empty_header(data)) + result = CURLE_OUT_OF_MEMORY; + } + + if(data->state.hresult) + infof(data, "ERROR in 1xx, bail out"); +} + +/* + * Curl_http() gets called from the generic multi_do() function when an HTTP + * request is to be performed. This creates and sends a properly constructed + * HTTP request. + */ +CURLcode Curl_http(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + struct hyptransfer *h = &data->hyp; + hyper_io *io = NULL; + hyper_clientconn_options *options = NULL; + hyper_task *task = NULL; /* for the handshake */ + hyper_task *sendtask = NULL; /* for the send */ + hyper_clientconn *client = NULL; + hyper_request *req = NULL; + hyper_headers *headers = NULL; + hyper_task *handshake = NULL; + CURLcode result; + const char *p_accept; /* Accept: string */ + const char *method; + Curl_HttpReq httpreq; + const char *te = NULL; /* transfer-encoding */ + hyper_code rc; + + /* Always consider the DO phase done after this function call, even if there + may be parts of the request that is not yet sent, since we can deal with + the rest of the request in the PERFORM phase. */ + *done = TRUE; + Curl_client_cleanup(data); + + infof(data, "Time for the Hyper dance"); + memset(h, 0, sizeof(struct hyptransfer)); + + result = Curl_http_host(data, conn); + if(result) + return result; + + Curl_http_method(data, conn, &method, &httpreq); + + DEBUGASSERT(data->req.bytecount == 0); + + /* setup the authentication headers */ + { + char *pq = NULL; + if(data->state.up.query) { + pq = aprintf("%s?%s", data->state.up.path, data->state.up.query); + if(!pq) + return CURLE_OUT_OF_MEMORY; + } + result = Curl_http_output_auth(data, conn, method, httpreq, + (pq ? pq : data->state.up.path), FALSE); + free(pq); + if(result) + return result; + } + + result = Curl_http_resume(data, conn, httpreq); + if(result) + return result; + + result = Curl_http_range(data, httpreq); + if(result) + return result; + + result = Curl_http_useragent(data); + if(result) + return result; + + io = hyper_io_new(); + if(!io) { + failf(data, "Couldn't create hyper IO"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + /* tell Hyper how to read/write network data */ + hyper_io_set_userdata(io, data); + hyper_io_set_read(io, Curl_hyper_recv); + hyper_io_set_write(io, Curl_hyper_send); + + /* create an executor to poll futures */ + if(!h->exec) { + h->exec = hyper_executor_new(); + if(!h->exec) { + failf(data, "Couldn't create hyper executor"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + } + + options = hyper_clientconn_options_new(); + if(!options) { + failf(data, "Couldn't create hyper client options"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + if(conn->alpn == CURL_HTTP_VERSION_2) { + failf(data, "ALPN protocol h2 not supported with Hyper"); + result = CURLE_UNSUPPORTED_PROTOCOL; + goto error; + } + hyper_clientconn_options_set_preserve_header_case(options, 1); + hyper_clientconn_options_set_preserve_header_order(options, 1); + hyper_clientconn_options_http1_allow_multiline_headers(options, 1); + + hyper_clientconn_options_exec(options, h->exec); + + /* "Both the `io` and the `options` are consumed in this function call" */ + handshake = hyper_clientconn_handshake(io, options); + if(!handshake) { + failf(data, "Couldn't create hyper client handshake"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + io = NULL; + options = NULL; + + if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) { + failf(data, "Couldn't hyper_executor_push the handshake"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + handshake = NULL; /* ownership passed on */ + + task = hyper_executor_poll(h->exec); + if(!task) { + failf(data, "Couldn't hyper_executor_poll the handshake"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + client = hyper_task_value(task); + hyper_task_free(task); + + req = hyper_request_new(); + if(!req) { + failf(data, "Couldn't hyper_request_new"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + if(!Curl_use_http_1_1plus(data, conn)) { + if(HYPERE_OK != hyper_request_set_version(req, + HYPER_HTTP_VERSION_1_0)) { + failf(data, "error setting HTTP version"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + } + else { + if(!data->state.disableexpect) { + data->state.expect100header = TRUE; + } + } + + if(hyper_request_set_method(req, (uint8_t *)method, strlen(method))) { + failf(data, "error setting method"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + result = request_target(data, conn, method, req); + if(result) + goto error; + + headers = hyper_request_headers(req); + if(!headers) { + failf(data, "hyper_request_headers"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + rc = hyper_request_on_informational(req, http1xx_cb, data); + if(rc) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + result = Curl_http_body(data, conn, httpreq, &te); + if(result) + goto error; + + if(data->state.aptr.host) { + result = Curl_hyper_header(data, headers, data->state.aptr.host); + if(result) + goto error; + } + + if(data->state.aptr.proxyuserpwd) { + result = Curl_hyper_header(data, headers, data->state.aptr.proxyuserpwd); + if(result) + goto error; + } + + if(data->state.aptr.userpwd) { + result = Curl_hyper_header(data, headers, data->state.aptr.userpwd); + if(result) + goto error; + } + + if((data->state.use_range && data->state.aptr.rangeline)) { + result = Curl_hyper_header(data, headers, data->state.aptr.rangeline); + if(result) + goto error; + } + + if(data->set.str[STRING_USERAGENT] && + *data->set.str[STRING_USERAGENT] && + data->state.aptr.uagent) { + result = Curl_hyper_header(data, headers, data->state.aptr.uagent); + if(result) + goto error; + } + + p_accept = Curl_checkheaders(data, + STRCONST("Accept"))?NULL:"Accept: */*\r\n"; + if(p_accept) { + result = Curl_hyper_header(data, headers, p_accept); + if(result) + goto error; + } + if(te) { + result = Curl_hyper_header(data, headers, te); + if(result) + goto error; + } + +#ifndef CURL_DISABLE_ALTSVC + if(conn->bits.altused && !Curl_checkheaders(data, STRCONST("Alt-Used"))) { + char *altused = aprintf("Alt-Used: %s:%d\r\n", + conn->conn_to_host.name, conn->conn_to_port); + if(!altused) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + result = Curl_hyper_header(data, headers, altused); + if(result) + goto error; + free(altused); + } +#endif + +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy && !conn->bits.tunnel_proxy && + !Curl_checkheaders(data, STRCONST("Proxy-Connection")) && + !Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) { + result = Curl_hyper_header(data, headers, "Proxy-Connection: Keep-Alive"); + if(result) + goto error; + } +#endif + + Curl_safefree(data->state.aptr.ref); + if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer"))) { + data->state.aptr.ref = aprintf("Referer: %s\r\n", data->state.referer); + if(!data->state.aptr.ref) + result = CURLE_OUT_OF_MEMORY; + else + result = Curl_hyper_header(data, headers, data->state.aptr.ref); + if(result) + goto error; + } + +#ifdef HAVE_LIBZ + /* we only consider transfer-encoding magic if libz support is built-in */ + result = Curl_transferencode(data); + if(result) + goto error; + result = Curl_hyper_header(data, headers, data->state.aptr.te); + if(result) + goto error; +#endif + + if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && + data->set.str[STRING_ENCODING]) { + Curl_safefree(data->state.aptr.accept_encoding); + data->state.aptr.accept_encoding = + aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); + if(!data->state.aptr.accept_encoding) + result = CURLE_OUT_OF_MEMORY; + else + result = Curl_hyper_header(data, headers, + data->state.aptr.accept_encoding); + if(result) + goto error; + } + else + Curl_safefree(data->state.aptr.accept_encoding); + + result = cookies(data, conn, headers); + if(result) + goto error; + + if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) + result = Curl_ws_request(data, headers); + + result = Curl_add_timecondition(data, headers); + if(result) + goto error; + + result = Curl_add_custom_headers(data, FALSE, headers); + if(result) + goto error; + + result = bodysend(data, conn, headers, req, httpreq); + if(result) + goto error; + + Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"\r\n", 2); + + if(data->req.upload_chunky && conn->bits.authneg) { + data->req.upload_chunky = TRUE; + } + else { + data->req.upload_chunky = FALSE; + } + sendtask = hyper_clientconn_send(client, req); + if(!sendtask) { + failf(data, "hyper_clientconn_send"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + req = NULL; + + if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) { + failf(data, "Couldn't hyper_executor_push the send"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + sendtask = NULL; /* ownership passed on */ + + hyper_clientconn_free(client); + client = NULL; + + if((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) { + /* HTTP GET/HEAD download */ + Curl_pgrsSetUploadSize(data, 0); /* nothing */ + Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); + } + conn->datastream = Curl_hyper_stream; + if(data->state.expect100header) + /* Timeout count starts now since with Hyper we don't know exactly when + the full request has been sent. */ + data->req.start100 = Curl_now(); + + /* clear userpwd and proxyuserpwd to avoid reusing old credentials + * from reused connections */ + Curl_safefree(data->state.aptr.userpwd); + Curl_safefree(data->state.aptr.proxyuserpwd); + return CURLE_OK; +error: + DEBUGASSERT(result); + if(io) + hyper_io_free(io); + + if(options) + hyper_clientconn_options_free(options); + + if(handshake) + hyper_task_free(handshake); + + if(client) + hyper_clientconn_free(client); + + if(req) + hyper_request_free(req); + + return result; +} + +void Curl_hyper_done(struct Curl_easy *data) +{ + struct hyptransfer *h = &data->hyp; + if(h->exec) { + hyper_executor_free(h->exec); + h->exec = NULL; + } + if(h->read_waker) { + hyper_waker_free(h->read_waker); + h->read_waker = NULL; + } + if(h->write_waker) { + hyper_waker_free(h->write_waker); + h->write_waker = NULL; + } + if(h->exp100_waker) { + hyper_waker_free(h->exp100_waker); + h->exp100_waker = NULL; + } +} + +#endif /* !defined(CURL_DISABLE_HTTP) && defined(USE_HYPER) */ diff --git a/Utilities/cmcurl/lib/c-hyper.h b/Utilities/cmcurl/lib/c-hyper.h new file mode 100644 index 0000000..0c7de90 --- /dev/null +++ b/Utilities/cmcurl/lib/c-hyper.h @@ -0,0 +1,59 @@ +#ifndef HEADER_CURL_HYPER_H +#define HEADER_CURL_HYPER_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_HYPER) + +#include <hyper.h> + +/* per-transfer data for the Hyper backend */ +struct hyptransfer { + hyper_waker *write_waker; + hyper_waker *read_waker; + const hyper_executor *exec; + hyper_waker *exp100_waker; + hyper_waker *send_body_waker; +}; + +size_t Curl_hyper_recv(void *userp, hyper_context *ctx, + uint8_t *buf, size_t buflen); +size_t Curl_hyper_send(void *userp, hyper_context *ctx, + const uint8_t *buf, size_t buflen); +CURLcode Curl_hyper_stream(struct Curl_easy *data, + struct connectdata *conn, + int *didwhat, + bool *done, + int select_res); + +CURLcode Curl_hyper_header(struct Curl_easy *data, hyper_headers *headers, + const char *line); +void Curl_hyper_done(struct Curl_easy *); + +#else +#define Curl_hyper_done(x) + +#endif /* !defined(CURL_DISABLE_HTTP) && defined(USE_HYPER) */ +#endif /* HEADER_CURL_HYPER_H */ diff --git a/Utilities/cmcurl/lib/cf-h1-proxy.c b/Utilities/cmcurl/lib/cf-h1-proxy.c new file mode 100644 index 0000000..2e23b0b --- /dev/null +++ b/Utilities/cmcurl/lib/cf-h1-proxy.c @@ -0,0 +1,1114 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) + +#include <curl/curl.h> +#ifdef USE_HYPER +#include <hyper.h> +#endif +#include "urldata.h" +#include "dynbuf.h" +#include "sendf.h" +#include "http.h" +#include "http1.h" +#include "http_proxy.h" +#include "url.h" +#include "select.h" +#include "progress.h" +#include "cfilters.h" +#include "cf-h1-proxy.h" +#include "connect.h" +#include "curl_trc.h" +#include "curlx.h" +#include "vtls/vtls.h" +#include "transfer.h" +#include "multiif.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +typedef enum { + H1_TUNNEL_INIT, /* init/default/no tunnel state */ + H1_TUNNEL_CONNECT, /* CONNECT request is being send */ + H1_TUNNEL_RECEIVE, /* CONNECT answer is being received */ + H1_TUNNEL_RESPONSE, /* CONNECT response received completely */ + H1_TUNNEL_ESTABLISHED, + H1_TUNNEL_FAILED +} h1_tunnel_state; + +/* struct for HTTP CONNECT tunneling */ +struct h1_tunnel_state { + struct HTTP CONNECT; + struct dynbuf rcvbuf; + struct dynbuf request_data; + size_t nsent; + size_t headerlines; + enum keeponval { + KEEPON_DONE, + KEEPON_CONNECT, + KEEPON_IGNORE + } keepon; + curl_off_t cl; /* size of content to read and ignore */ + h1_tunnel_state tunnel_state; + BIT(chunked_encoding); + BIT(close_connection); +}; + + +static bool tunnel_is_established(struct h1_tunnel_state *ts) +{ + return ts && (ts->tunnel_state == H1_TUNNEL_ESTABLISHED); +} + +static bool tunnel_is_failed(struct h1_tunnel_state *ts) +{ + return ts && (ts->tunnel_state == H1_TUNNEL_FAILED); +} + +static CURLcode tunnel_reinit(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts) +{ + (void)data; + (void)cf; + DEBUGASSERT(ts); + Curl_dyn_reset(&ts->rcvbuf); + Curl_dyn_reset(&ts->request_data); + ts->tunnel_state = H1_TUNNEL_INIT; + ts->keepon = KEEPON_CONNECT; + ts->cl = 0; + ts->close_connection = FALSE; + return CURLE_OK; +} + +static CURLcode tunnel_init(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state **pts) +{ + struct h1_tunnel_state *ts; + CURLcode result; + + if(cf->conn->handler->flags & PROTOPT_NOTCPPROXY) { + failf(data, "%s cannot be done over CONNECT", cf->conn->handler->scheme); + return CURLE_UNSUPPORTED_PROTOCOL; + } + + /* we might need the upload buffer for streaming a partial request */ + result = Curl_get_upload_buffer(data); + if(result) + return result; + + ts = calloc(1, sizeof(*ts)); + if(!ts) + return CURLE_OUT_OF_MEMORY; + + infof(data, "allocate connect buffer"); + + Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); + Curl_dyn_init(&ts->request_data, DYN_HTTP_REQUEST); + + *pts = ts; + connkeep(cf->conn, "HTTP proxy CONNECT"); + return tunnel_reinit(cf, data, ts); +} + +static void h1_tunnel_go_state(struct Curl_cfilter *cf, + struct h1_tunnel_state *ts, + h1_tunnel_state new_state, + struct Curl_easy *data) +{ + if(ts->tunnel_state == new_state) + return; + /* leaving this one */ + switch(ts->tunnel_state) { + case H1_TUNNEL_CONNECT: + data->req.ignorebody = FALSE; + break; + default: + break; + } + /* entering this one */ + switch(new_state) { + case H1_TUNNEL_INIT: + CURL_TRC_CF(data, cf, "new tunnel state 'init'"); + tunnel_reinit(cf, data, ts); + break; + + case H1_TUNNEL_CONNECT: + CURL_TRC_CF(data, cf, "new tunnel state 'connect'"); + ts->tunnel_state = H1_TUNNEL_CONNECT; + ts->keepon = KEEPON_CONNECT; + Curl_dyn_reset(&ts->rcvbuf); + break; + + case H1_TUNNEL_RECEIVE: + CURL_TRC_CF(data, cf, "new tunnel state 'receive'"); + ts->tunnel_state = H1_TUNNEL_RECEIVE; + break; + + case H1_TUNNEL_RESPONSE: + CURL_TRC_CF(data, cf, "new tunnel state 'response'"); + ts->tunnel_state = H1_TUNNEL_RESPONSE; + break; + + case H1_TUNNEL_ESTABLISHED: + CURL_TRC_CF(data, cf, "new tunnel state 'established'"); + infof(data, "CONNECT phase completed"); + data->state.authproxy.done = TRUE; + data->state.authproxy.multipass = FALSE; + /* FALLTHROUGH */ + case H1_TUNNEL_FAILED: + if(new_state == H1_TUNNEL_FAILED) + CURL_TRC_CF(data, cf, "new tunnel state 'failed'"); + ts->tunnel_state = new_state; + Curl_dyn_reset(&ts->rcvbuf); + Curl_dyn_reset(&ts->request_data); + /* restore the protocol pointer */ + data->info.httpcode = 0; /* clear it as it might've been used for the + proxy */ + /* If a proxy-authorization header was used for the proxy, then we should + make sure that it isn't accidentally used for the document request + after we've connected. So let's free and clear it here. */ + Curl_safefree(data->state.aptr.proxyuserpwd); +#ifdef USE_HYPER + data->state.hconnect = FALSE; +#endif + break; + } +} + +static void tunnel_free(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct h1_tunnel_state *ts = cf->ctx; + if(ts) { + h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); + Curl_dyn_free(&ts->rcvbuf); + Curl_dyn_free(&ts->request_data); + free(ts); + cf->ctx = NULL; + } +} + +#ifndef USE_HYPER +static CURLcode start_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts) +{ + struct httpreq *req = NULL; + int http_minor; + CURLcode result; + + /* This only happens if we've looped here due to authentication + reasons, and we don't really use the newly cloned URL here + then. Just free() it. */ + Curl_safefree(data->req.newurl); + + result = Curl_http_proxy_create_CONNECT(&req, cf, data, 1); + if(result) + goto out; + + infof(data, "Establish HTTP proxy tunnel to %s", req->authority); + + Curl_dyn_reset(&ts->request_data); + ts->nsent = 0; + ts->headerlines = 0; + http_minor = (cf->conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? 0 : 1; + + result = Curl_h1_req_write_head(req, http_minor, &ts->request_data); + +out: + if(result) + failf(data, "Failed sending CONNECT to proxy"); + if(req) + Curl_http_req_free(req); + return result; +} + +static CURLcode send_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts, + bool *done) +{ + char *buf = Curl_dyn_ptr(&ts->request_data); + size_t request_len = Curl_dyn_len(&ts->request_data); + size_t blen = request_len; + CURLcode result = CURLE_OK; + ssize_t nwritten; + + if(blen <= ts->nsent) + goto out; /* we are done */ + + blen -= ts->nsent; + buf += ts->nsent; + + nwritten = cf->next->cft->do_send(cf->next, data, buf, blen, &result); + if(nwritten < 0) { + if(result == CURLE_AGAIN) { + result = CURLE_OK; + } + goto out; + } + + DEBUGASSERT(blen >= (size_t)nwritten); + ts->nsent += (size_t)nwritten; + Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)nwritten); + +out: + if(result) + failf(data, "Failed sending CONNECT to proxy"); + *done = (!result && (ts->nsent >= request_len)); + return result; +} + +static CURLcode on_resp_header(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts, + const char *header) +{ + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + (void)cf; + + if((checkprefix("WWW-Authenticate:", header) && + (401 == k->httpcode)) || + (checkprefix("Proxy-authenticate:", header) && + (407 == k->httpcode))) { + + bool proxy = (k->httpcode == 407) ? TRUE : FALSE; + char *auth = Curl_copy_header_value(header); + if(!auth) + return CURLE_OUT_OF_MEMORY; + + CURL_TRC_CF(data, cf, "CONNECT: fwd auth header '%s'", header); + result = Curl_http_input_auth(data, proxy, auth); + + free(auth); + + if(result) + return result; + } + else if(checkprefix("Content-Length:", header)) { + if(k->httpcode/100 == 2) { + /* A client MUST ignore any Content-Length or Transfer-Encoding + header fields received in a successful response to CONNECT. + "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */ + infof(data, "Ignoring Content-Length in CONNECT %03d response", + k->httpcode); + } + else { + (void)curlx_strtoofft(header + strlen("Content-Length:"), + NULL, 10, &ts->cl); + } + } + else if(Curl_compareheader(header, + STRCONST("Connection:"), STRCONST("close"))) + ts->close_connection = TRUE; + else if(checkprefix("Transfer-Encoding:", header)) { + if(k->httpcode/100 == 2) { + /* A client MUST ignore any Content-Length or Transfer-Encoding + header fields received in a successful response to CONNECT. + "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */ + infof(data, "Ignoring Transfer-Encoding in " + "CONNECT %03d response", k->httpcode); + } + else if(Curl_compareheader(header, + STRCONST("Transfer-Encoding:"), + STRCONST("chunked"))) { + infof(data, "CONNECT responded chunked"); + ts->chunked_encoding = TRUE; + /* init our chunky engine */ + Curl_httpchunk_init(data); + } + } + else if(Curl_compareheader(header, + STRCONST("Proxy-Connection:"), + STRCONST("close"))) + ts->close_connection = TRUE; + else if(!strncmp(header, "HTTP/1.", 7) && + ((header[7] == '0') || (header[7] == '1')) && + (header[8] == ' ') && + ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) && + !ISDIGIT(header[12])) { + /* store the HTTP code from the proxy */ + data->info.httpproxycode = k->httpcode = (header[9] - '0') * 100 + + (header[10] - '0') * 10 + (header[11] - '0'); + } + return result; +} + +static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts, + bool *done) +{ + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data); + char *linep; + size_t perline; + int error, writetype; + +#define SELECT_OK 0 +#define SELECT_ERROR 1 + + error = SELECT_OK; + *done = FALSE; + + if(!Curl_conn_data_pending(data, cf->sockindex)) + return CURLE_OK; + + while(ts->keepon) { + ssize_t nread; + char byte; + + /* Read one byte at a time to avoid a race condition. Wait at most one + second before looping to ensure continuous pgrsUpdates. */ + result = Curl_read(data, tunnelsocket, &byte, 1, &nread); + if(result == CURLE_AGAIN) + /* socket buffer drained, return */ + return CURLE_OK; + + if(Curl_pgrsUpdate(data)) + return CURLE_ABORTED_BY_CALLBACK; + + if(result) { + ts->keepon = KEEPON_DONE; + break; + } + + if(nread <= 0) { + if(data->set.proxyauth && data->state.authproxy.avail && + data->state.aptr.proxyuserpwd) { + /* proxy auth was requested and there was proxy auth available, + then deem this as "mere" proxy disconnect */ + ts->close_connection = TRUE; + infof(data, "Proxy CONNECT connection closed"); + } + else { + error = SELECT_ERROR; + failf(data, "Proxy CONNECT aborted"); + } + ts->keepon = KEEPON_DONE; + break; + } + + if(ts->keepon == KEEPON_IGNORE) { + /* This means we are currently ignoring a response-body */ + + if(ts->cl) { + /* A Content-Length based body: simply count down the counter + and make sure to break out of the loop when we're done! */ + ts->cl--; + if(ts->cl <= 0) { + ts->keepon = KEEPON_DONE; + break; + } + } + else { + /* chunked-encoded body, so we need to do the chunked dance + properly to know when the end of the body is reached */ + CHUNKcode r; + CURLcode extra; + size_t consumed = 0; + + /* now parse the chunked piece of data so that we can + properly tell when the stream ends */ + r = Curl_httpchunk_read(data, &byte, 1, &consumed, &extra); + if(r == CHUNKE_STOP) { + /* we're done reading chunks! */ + infof(data, "chunk reading DONE"); + ts->keepon = KEEPON_DONE; + } + } + continue; + } + + if(Curl_dyn_addn(&ts->rcvbuf, &byte, 1)) { + failf(data, "CONNECT response too large"); + return CURLE_RECV_ERROR; + } + + /* if this is not the end of a header line then continue */ + if(byte != 0x0a) + continue; + + ts->headerlines++; + linep = Curl_dyn_ptr(&ts->rcvbuf); + perline = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ + + /* output debug if that is requested */ + Curl_debug(data, CURLINFO_HEADER_IN, linep, perline); + + /* send the header to the callback */ + writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | + (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); + result = Curl_client_write(data, writetype, linep, perline); + if(result) + return result; + + result = Curl_bump_headersize(data, perline, TRUE); + if(result) + return result; + + /* Newlines are CRLF, so the CR is ignored as the line isn't + really terminated until the LF comes. Treat a following CR + as end-of-headers as well.*/ + + if(('\r' == linep[0]) || + ('\n' == linep[0])) { + /* end of response-headers from the proxy */ + + if((407 == k->httpcode) && !data->state.authproblem) { + /* If we get a 407 response code with content length + when we have no auth problem, we must ignore the + whole response-body */ + ts->keepon = KEEPON_IGNORE; + + if(ts->cl) { + infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T + " bytes of response-body", ts->cl); + } + else if(ts->chunked_encoding) { + CHUNKcode r; + CURLcode extra; + size_t consumed = 0; + + infof(data, "Ignore chunked response-body"); + + /* We set ignorebody true here since the chunked decoder + function will acknowledge that. Pay attention so that this is + cleared again when this function returns! */ + k->ignorebody = TRUE; + + if(linep[1] == '\n') + /* this can only be a LF if the letter at index 0 was a CR */ + linep++; + + /* now parse the chunked piece of data so that we can properly + tell when the stream ends */ + r = Curl_httpchunk_read(data, linep + 1, 1, &consumed, &extra); + if(r == CHUNKE_STOP) { + /* we're done reading chunks! */ + infof(data, "chunk reading DONE"); + ts->keepon = KEEPON_DONE; + } + } + else { + /* without content-length or chunked encoding, we + can't keep the connection alive since the close is + the end signal so we bail out at once instead */ + CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked"); + ts->keepon = KEEPON_DONE; + } + } + else { + ts->keepon = KEEPON_DONE; + } + + DEBUGASSERT(ts->keepon == KEEPON_IGNORE + || ts->keepon == KEEPON_DONE); + continue; + } + + result = on_resp_header(cf, data, ts, linep); + if(result) + return result; + + Curl_dyn_reset(&ts->rcvbuf); + } /* while there's buffer left and loop is requested */ + + if(error) + result = CURLE_RECV_ERROR; + *done = (ts->keepon == KEEPON_DONE); + if(!result && *done && data->info.httpproxycode/100 != 2) { + /* Deal with the possibly already received authenticate + headers. 'newurl' is set to a new URL if we must loop. */ + result = Curl_http_auth_act(data); + } + return result; +} + +#else /* USE_HYPER */ + +static CURLcode CONNECT_host(struct Curl_cfilter *cf, + struct Curl_easy *data, + char **pauthority, + char **phost_header) +{ + const char *hostname; + int port; + bool ipv6_ip; + CURLcode result; + char *authority; /* for CONNECT, the destination host + port */ + char *host_header = NULL; /* Host: authority */ + + result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); + if(result) + return result; + + authority = aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"", + port); + if(!authority) + return CURLE_OUT_OF_MEMORY; + + /* If user is not overriding the Host header later */ + if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) { + host_header = aprintf("Host: %s\r\n", authority); + if(!host_header) { + free(authority); + return CURLE_OUT_OF_MEMORY; + } + } + *pauthority = authority; + *phost_header = host_header; + return CURLE_OK; +} + +/* The Hyper version of CONNECT */ +static CURLcode start_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts) +{ + struct connectdata *conn = cf->conn; + struct hyptransfer *h = &data->hyp; + curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data); + hyper_io *io = NULL; + hyper_request *req = NULL; + hyper_headers *headers = NULL; + hyper_clientconn_options *options = NULL; + hyper_task *handshake = NULL; + hyper_task *task = NULL; /* for the handshake */ + hyper_clientconn *client = NULL; + hyper_task *sendtask = NULL; /* for the send */ + char *authority = NULL; /* for CONNECT */ + char *host_header = NULL; /* Host: */ + CURLcode result = CURLE_OUT_OF_MEMORY; + (void)ts; + + io = hyper_io_new(); + if(!io) { + failf(data, "Couldn't create hyper IO"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + /* tell Hyper how to read/write network data */ + hyper_io_set_userdata(io, data); + hyper_io_set_read(io, Curl_hyper_recv); + hyper_io_set_write(io, Curl_hyper_send); + conn->sockfd = tunnelsocket; + + data->state.hconnect = TRUE; + + /* create an executor to poll futures */ + if(!h->exec) { + h->exec = hyper_executor_new(); + if(!h->exec) { + failf(data, "Couldn't create hyper executor"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + } + + options = hyper_clientconn_options_new(); + if(!options) { + failf(data, "Couldn't create hyper client options"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + hyper_clientconn_options_set_preserve_header_case(options, 1); + hyper_clientconn_options_set_preserve_header_order(options, 1); + + hyper_clientconn_options_exec(options, h->exec); + + /* "Both the `io` and the `options` are consumed in this function + call" */ + handshake = hyper_clientconn_handshake(io, options); + if(!handshake) { + failf(data, "Couldn't create hyper client handshake"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + io = NULL; + options = NULL; + + if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) { + failf(data, "Couldn't hyper_executor_push the handshake"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + handshake = NULL; /* ownership passed on */ + + task = hyper_executor_poll(h->exec); + if(!task) { + failf(data, "Couldn't hyper_executor_poll the handshake"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + client = hyper_task_value(task); + hyper_task_free(task); + + req = hyper_request_new(); + if(!req) { + failf(data, "Couldn't hyper_request_new"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + if(hyper_request_set_method(req, (uint8_t *)"CONNECT", + strlen("CONNECT"))) { + failf(data, "error setting method"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + /* This only happens if we've looped here due to authentication + reasons, and we don't really use the newly cloned URL here + then. Just free() it. */ + Curl_safefree(data->req.newurl); + + result = CONNECT_host(cf, data, &authority, &host_header); + if(result) + goto error; + + infof(data, "Establish HTTP proxy tunnel to %s", authority); + + if(hyper_request_set_uri(req, (uint8_t *)authority, + strlen(authority))) { + failf(data, "error setting path"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + if(data->set.verbose) { + char *se = aprintf("CONNECT %s HTTP/1.1\r\n", authority); + if(!se) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + Curl_debug(data, CURLINFO_HEADER_OUT, se, strlen(se)); + free(se); + } + /* Setup the proxy-authorization header, if any */ + result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET, + authority, TRUE); + if(result) + goto error; + Curl_safefree(authority); + + /* default is 1.1 */ + if((conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) && + (HYPERE_OK != hyper_request_set_version(req, + HYPER_HTTP_VERSION_1_0))) { + failf(data, "error setting HTTP version"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + headers = hyper_request_headers(req); + if(!headers) { + failf(data, "hyper_request_headers"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + if(host_header) { + result = Curl_hyper_header(data, headers, host_header); + if(result) + goto error; + Curl_safefree(host_header); + } + + if(data->state.aptr.proxyuserpwd) { + result = Curl_hyper_header(data, headers, + data->state.aptr.proxyuserpwd); + if(result) + goto error; + } + + if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) && + data->set.str[STRING_USERAGENT]) { + struct dynbuf ua; + Curl_dyn_init(&ua, DYN_HTTP_REQUEST); + result = Curl_dyn_addf(&ua, "User-Agent: %s\r\n", + data->set.str[STRING_USERAGENT]); + if(result) + goto error; + result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&ua)); + if(result) + goto error; + Curl_dyn_free(&ua); + } + + if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) { + result = Curl_hyper_header(data, headers, + "Proxy-Connection: Keep-Alive"); + if(result) + goto error; + } + + result = Curl_add_custom_headers(data, TRUE, headers); + if(result) + goto error; + + sendtask = hyper_clientconn_send(client, req); + if(!sendtask) { + failf(data, "hyper_clientconn_send"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + req = NULL; + + if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) { + failf(data, "Couldn't hyper_executor_push the send"); + result = CURLE_OUT_OF_MEMORY; + goto error; + } + sendtask = NULL; /* ownership passed on */ + + hyper_clientconn_free(client); + client = NULL; + +error: + free(host_header); + free(authority); + if(io) + hyper_io_free(io); + if(options) + hyper_clientconn_options_free(options); + if(handshake) + hyper_task_free(handshake); + if(client) + hyper_clientconn_free(client); + if(req) + hyper_request_free(req); + + return result; +} + +static CURLcode send_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts, + bool *done) +{ + struct hyptransfer *h = &data->hyp; + struct connectdata *conn = cf->conn; + hyper_task *task = NULL; + hyper_error *hypererr = NULL; + CURLcode result = CURLE_OK; + + (void)ts; + (void)conn; + do { + task = hyper_executor_poll(h->exec); + if(task) { + bool error = hyper_task_type(task) == HYPER_TASK_ERROR; + if(error) + hypererr = hyper_task_value(task); + hyper_task_free(task); + if(error) { + /* this could probably use a better error code? */ + result = CURLE_OUT_OF_MEMORY; + goto error; + } + } + } while(task); +error: + *done = (result == CURLE_OK); + if(hypererr) { + uint8_t errbuf[256]; + size_t errlen = hyper_error_print(hypererr, errbuf, sizeof(errbuf)); + failf(data, "Hyper: %.*s", (int)errlen, errbuf); + hyper_error_free(hypererr); + } + return result; +} + +static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts, + bool *done) +{ + struct hyptransfer *h = &data->hyp; + CURLcode result; + int didwhat; + + (void)ts; + *done = FALSE; + result = Curl_hyper_stream(data, cf->conn, &didwhat, done, + CURL_CSELECT_IN | CURL_CSELECT_OUT); + if(result || !*done) + return result; + if(h->exec) { + hyper_executor_free(h->exec); + h->exec = NULL; + } + if(h->read_waker) { + hyper_waker_free(h->read_waker); + h->read_waker = NULL; + } + if(h->write_waker) { + hyper_waker_free(h->write_waker); + h->write_waker = NULL; + } + return result; +} + +#endif /* USE_HYPER */ + +static CURLcode H1_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts) +{ + struct connectdata *conn = cf->conn; + CURLcode result; + bool done; + + if(tunnel_is_established(ts)) + return CURLE_OK; + if(tunnel_is_failed(ts)) + return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */ + + do { + timediff_t check; + + check = Curl_timeleft(data, NULL, TRUE); + if(check <= 0) { + failf(data, "Proxy CONNECT aborted due to timeout"); + result = CURLE_OPERATION_TIMEDOUT; + goto out; + } + + switch(ts->tunnel_state) { + case H1_TUNNEL_INIT: + /* Prepare the CONNECT request and make a first attempt to send. */ + CURL_TRC_CF(data, cf, "CONNECT start"); + result = start_CONNECT(cf, data, ts); + if(result) + goto out; + h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data); + /* FALLTHROUGH */ + + case H1_TUNNEL_CONNECT: + /* see that the request is completely sent */ + CURL_TRC_CF(data, cf, "CONNECT send"); + result = send_CONNECT(cf, data, ts, &done); + if(result || !done) + goto out; + h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data); + /* FALLTHROUGH */ + + case H1_TUNNEL_RECEIVE: + /* read what is there */ + CURL_TRC_CF(data, cf, "CONNECT receive"); + result = recv_CONNECT_resp(cf, data, ts, &done); + if(Curl_pgrsUpdate(data)) { + result = CURLE_ABORTED_BY_CALLBACK; + goto out; + } + /* error or not complete yet. return for more multi-multi */ + if(result || !done) + goto out; + /* got it */ + h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data); + /* FALLTHROUGH */ + + case H1_TUNNEL_RESPONSE: + CURL_TRC_CF(data, cf, "CONNECT response"); + if(data->req.newurl) { + /* not the "final" response, we need to do a follow up request. + * If the other side indicated a connection close, or if someone + * else told us to close this connection, do so now. + */ + if(ts->close_connection || conn->bits.close) { + /* Close this filter and the sub-chain, re-connect the + * sub-chain and continue. Closing this filter will + * reset our tunnel state. To avoid recursion, we return + * and expect to be called again. + */ + CURL_TRC_CF(data, cf, "CONNECT need to close+open"); + infof(data, "Connect me again please"); + Curl_conn_cf_close(cf, data); + connkeep(conn, "HTTP proxy CONNECT"); + result = Curl_conn_cf_connect(cf->next, data, FALSE, &done); + goto out; + } + else { + /* staying on this connection, reset state */ + h1_tunnel_go_state(cf, ts, H1_TUNNEL_INIT, data); + } + } + break; + + default: + break; + } + + } while(data->req.newurl); + + DEBUGASSERT(ts->tunnel_state == H1_TUNNEL_RESPONSE); + if(data->info.httpproxycode/100 != 2) { + /* a non-2xx response and we have no next url to try. */ + Curl_safefree(data->req.newurl); + /* failure, close this connection to avoid reuse */ + streamclose(conn, "proxy CONNECT failure"); + h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); + failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode); + return CURLE_RECV_ERROR; + } + /* 2xx response, SUCCESS! */ + h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data); + infof(data, "CONNECT tunnel established, response %d", + data->info.httpproxycode); + result = CURLE_OK; + +out: + if(result) + h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); + return result; +} + +static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + CURLcode result; + struct h1_tunnel_state *ts = cf->ctx; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + CURL_TRC_CF(data, cf, "connect"); + result = cf->next->cft->do_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + + *done = FALSE; + if(!ts) { + result = tunnel_init(cf, data, &ts); + if(result) + return result; + cf->ctx = ts; + } + + /* TODO: can we do blocking? */ + /* We want "seamless" operations through HTTP proxy tunnel */ + + result = H1_CONNECT(cf, data, ts); + if(result) + goto out; + Curl_safefree(data->state.aptr.proxyuserpwd); + +out: + *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx); + if(*done) { + cf->connected = TRUE; + tunnel_free(cf, data); + } + return result; +} + +static void cf_h1_proxy_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct h1_tunnel_state *ts = cf->ctx; + + if(!cf->connected) { + /* If we are not connected, but the filter "below" is + * and not waiting on something, we are tunneling. */ + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); + if(ts) { + /* when we've sent a CONNECT to a proxy, we should rather either + wait for the socket to become readable to be able to get the + response headers or if we're still sending the request, wait + for write. */ + if(ts->CONNECT.sending == HTTPSEND_REQUEST) + Curl_pollset_set_out_only(data, ps, sock); + else + Curl_pollset_set_in_only(data, ps, sock); + } + else + Curl_pollset_set_out_only(data, ps, sock); + } +} + +static void cf_h1_proxy_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURL_TRC_CF(data, cf, "destroy"); + tunnel_free(cf, data); +} + +static void cf_h1_proxy_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURL_TRC_CF(data, cf, "close"); + cf->connected = FALSE; + if(cf->ctx) { + h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data); + } + if(cf->next) + cf->next->cft->do_close(cf->next, data); +} + + +struct Curl_cftype Curl_cft_h1_proxy = { + "H1-PROXY", + CF_TYPE_IP_CONNECT, + 0, + cf_h1_proxy_destroy, + cf_h1_proxy_connect, + cf_h1_proxy_close, + Curl_cf_http_proxy_get_host, + cf_h1_proxy_adjust_pollset, + Curl_cf_def_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + CURLcode result; + + (void)data; + result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, NULL); + if(!result) + Curl_conn_cf_insert_after(cf_at, cf); + return result; +} + +#endif /* !CURL_DISABLE_PROXY && ! CURL_DISABLE_HTTP */ diff --git a/Utilities/cmcurl/lib/cf-h1-proxy.h b/Utilities/cmcurl/lib/cf-h1-proxy.h new file mode 100644 index 0000000..ac5bed0 --- /dev/null +++ b/Utilities/cmcurl/lib/cf-h1-proxy.h @@ -0,0 +1,39 @@ +#ifndef HEADER_CURL_H1_PROXY_H +#define HEADER_CURL_H1_PROXY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) + +CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf, + struct Curl_easy *data); + +extern struct Curl_cftype Curl_cft_h1_proxy; + + +#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ + +#endif /* HEADER_CURL_H1_PROXY_H */ diff --git a/Utilities/cmcurl/lib/cf-h2-proxy.c b/Utilities/cmcurl/lib/cf-h2-proxy.c new file mode 100644 index 0000000..147acdc --- /dev/null +++ b/Utilities/cmcurl/lib/cf-h2-proxy.c @@ -0,0 +1,1567 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) + +#include <nghttp2/nghttp2.h> +#include "urldata.h" +#include "cfilters.h" +#include "connect.h" +#include "curl_trc.h" +#include "bufq.h" +#include "dynbuf.h" +#include "dynhds.h" +#include "http1.h" +#include "http2.h" +#include "http_proxy.h" +#include "multiif.h" +#include "cf-h2-proxy.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define PROXY_H2_CHUNK_SIZE (16*1024) + +#define PROXY_HTTP2_HUGE_WINDOW_SIZE (100 * 1024 * 1024) +#define H2_TUNNEL_WINDOW_SIZE (10 * 1024 * 1024) + +#define PROXY_H2_NW_RECV_CHUNKS (H2_TUNNEL_WINDOW_SIZE / PROXY_H2_CHUNK_SIZE) +#define PROXY_H2_NW_SEND_CHUNKS 1 + +#define H2_TUNNEL_RECV_CHUNKS (H2_TUNNEL_WINDOW_SIZE / PROXY_H2_CHUNK_SIZE) +#define H2_TUNNEL_SEND_CHUNKS ((128 * 1024) / PROXY_H2_CHUNK_SIZE) + + +typedef enum { + H2_TUNNEL_INIT, /* init/default/no tunnel state */ + H2_TUNNEL_CONNECT, /* CONNECT request is being send */ + H2_TUNNEL_RESPONSE, /* CONNECT response received completely */ + H2_TUNNEL_ESTABLISHED, + H2_TUNNEL_FAILED +} h2_tunnel_state; + +struct tunnel_stream { + struct http_resp *resp; + struct bufq recvbuf; + struct bufq sendbuf; + char *authority; + int32_t stream_id; + uint32_t error; + size_t upload_blocked_len; + h2_tunnel_state state; + BIT(has_final_response); + BIT(closed); + BIT(reset); +}; + +static CURLcode tunnel_stream_init(struct Curl_cfilter *cf, + struct tunnel_stream *ts) +{ + const char *hostname; + int port; + bool ipv6_ip; + CURLcode result; + + ts->state = H2_TUNNEL_INIT; + ts->stream_id = -1; + Curl_bufq_init2(&ts->recvbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_RECV_CHUNKS, + BUFQ_OPT_SOFT_LIMIT); + Curl_bufq_init(&ts->sendbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS); + + result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); + if(result) + return result; + + ts->authority = /* host:port with IPv6 support */ + aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"", port); + if(!ts->authority) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +static void tunnel_stream_clear(struct tunnel_stream *ts) +{ + Curl_http_resp_free(ts->resp); + Curl_bufq_free(&ts->recvbuf); + Curl_bufq_free(&ts->sendbuf); + Curl_safefree(ts->authority); + memset(ts, 0, sizeof(*ts)); + ts->state = H2_TUNNEL_INIT; +} + +static void h2_tunnel_go_state(struct Curl_cfilter *cf, + struct tunnel_stream *ts, + h2_tunnel_state new_state, + struct Curl_easy *data) +{ + (void)cf; + + if(ts->state == new_state) + return; + /* leaving this one */ + switch(ts->state) { + case H2_TUNNEL_CONNECT: + data->req.ignorebody = FALSE; + break; + default: + break; + } + /* entering this one */ + switch(new_state) { + case H2_TUNNEL_INIT: + CURL_TRC_CF(data, cf, "[%d] new tunnel state 'init'", ts->stream_id); + tunnel_stream_clear(ts); + break; + + case H2_TUNNEL_CONNECT: + CURL_TRC_CF(data, cf, "[%d] new tunnel state 'connect'", ts->stream_id); + ts->state = H2_TUNNEL_CONNECT; + break; + + case H2_TUNNEL_RESPONSE: + CURL_TRC_CF(data, cf, "[%d] new tunnel state 'response'", ts->stream_id); + ts->state = H2_TUNNEL_RESPONSE; + break; + + case H2_TUNNEL_ESTABLISHED: + CURL_TRC_CF(data, cf, "[%d] new tunnel state 'established'", + ts->stream_id); + infof(data, "CONNECT phase completed"); + data->state.authproxy.done = TRUE; + data->state.authproxy.multipass = FALSE; + /* FALLTHROUGH */ + case H2_TUNNEL_FAILED: + if(new_state == H2_TUNNEL_FAILED) + CURL_TRC_CF(data, cf, "[%d] new tunnel state 'failed'", ts->stream_id); + ts->state = new_state; + /* If a proxy-authorization header was used for the proxy, then we should + make sure that it isn't accidentally used for the document request + after we've connected. So let's free and clear it here. */ + Curl_safefree(data->state.aptr.proxyuserpwd); + break; + } +} + +struct cf_h2_proxy_ctx { + nghttp2_session *h2; + /* The easy handle used in the current filter call, cleared at return */ + struct cf_call_data call_data; + + struct bufq inbufq; /* network receive buffer */ + struct bufq outbufq; /* network send buffer */ + + struct tunnel_stream tunnel; /* our tunnel CONNECT stream */ + int32_t goaway_error; + int32_t last_stream_id; + BIT(conn_closed); + BIT(goaway); + BIT(nw_out_blocked); +}; + +/* How to access `call_data` from a cf_h2 filter */ +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) \ + ((struct cf_h2_proxy_ctx *)(cf)->ctx)->call_data + +static void cf_h2_proxy_ctx_clear(struct cf_h2_proxy_ctx *ctx) +{ + struct cf_call_data save = ctx->call_data; + + if(ctx->h2) { + nghttp2_session_del(ctx->h2); + } + Curl_bufq_free(&ctx->inbufq); + Curl_bufq_free(&ctx->outbufq); + tunnel_stream_clear(&ctx->tunnel); + memset(ctx, 0, sizeof(*ctx)); + ctx->call_data = save; +} + +static void cf_h2_proxy_ctx_free(struct cf_h2_proxy_ctx *ctx) +{ + if(ctx) { + cf_h2_proxy_ctx_clear(ctx); + free(ctx); + } +} + +static void drain_tunnel(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct tunnel_stream *tunnel) +{ + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(!tunnel->closed && !tunnel->reset && tunnel->upload_blocked_len) + bits |= CURL_CSELECT_OUT; + if(data->state.dselect_bits != bits) { + CURL_TRC_CF(data, cf, "[%d] DRAIN dselect_bits=%x", + tunnel->stream_id, bits); + data->state.dselect_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + +static ssize_t proxy_nw_in_reader(void *reader_ctx, + unsigned char *buf, size_t buflen, + CURLcode *err) +{ + struct Curl_cfilter *cf = reader_ctx; + ssize_t nread; + + if(cf) { + struct Curl_easy *data = CF_DATA_CURRENT(cf); + nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, buflen, err); + CURL_TRC_CF(data, cf, "[0] nw_in_reader(len=%zu) -> %zd, %d", + buflen, nread, *err); + } + else { + nread = 0; + } + return nread; +} + +static ssize_t proxy_h2_nw_out_writer(void *writer_ctx, + const unsigned char *buf, size_t buflen, + CURLcode *err) +{ + struct Curl_cfilter *cf = writer_ctx; + ssize_t nwritten; + + if(cf) { + struct Curl_easy *data = CF_DATA_CURRENT(cf); + nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen, + err); + CURL_TRC_CF(data, cf, "[0] nw_out_writer(len=%zu) -> %zd, %d", + buflen, nwritten, *err); + } + else { + nwritten = 0; + } + return nwritten; +} + +static int proxy_h2_client_new(struct Curl_cfilter *cf, + nghttp2_session_callbacks *cbs) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + nghttp2_option *o; + + int rc = nghttp2_option_new(&o); + if(rc) + return rc; + /* We handle window updates ourself to enforce buffer limits */ + nghttp2_option_set_no_auto_window_update(o, 1); +#if NGHTTP2_VERSION_NUM >= 0x013200 + /* with 1.50.0 */ + /* turn off RFC 9113 leading and trailing white spaces validation against + HTTP field value. */ + nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1); +#endif + rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o); + nghttp2_option_del(o); + return rc; +} + +static ssize_t on_session_send(nghttp2_session *h2, + const uint8_t *buf, size_t blen, + int flags, void *userp); +static int proxy_h2_on_frame_recv(nghttp2_session *session, + const nghttp2_frame *frame, + void *userp); +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static int proxy_h2_on_frame_send(nghttp2_session *session, + const nghttp2_frame *frame, + void *userp); +#endif +static int proxy_h2_on_stream_close(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, void *userp); +static int proxy_h2_on_header(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, + void *userp); +static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const uint8_t *mem, size_t len, void *userp); + +/* + * Initialize the cfilter context + */ +static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OUT_OF_MEMORY; + nghttp2_session_callbacks *cbs = NULL; + int rc; + + DEBUGASSERT(!ctx->h2); + memset(&ctx->tunnel, 0, sizeof(ctx->tunnel)); + + 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); + + if(tunnel_stream_init(cf, &ctx->tunnel)) + goto out; + + rc = nghttp2_session_callbacks_new(&cbs); + if(rc) { + failf(data, "Couldn't initialize nghttp2 callbacks"); + goto out; + } + + nghttp2_session_callbacks_set_send_callback(cbs, on_session_send); + nghttp2_session_callbacks_set_on_frame_recv_callback( + cbs, proxy_h2_on_frame_recv); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + nghttp2_session_callbacks_set_on_frame_send_callback(cbs, + proxy_h2_on_frame_send); +#endif + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + cbs, tunnel_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback( + cbs, proxy_h2_on_stream_close); + nghttp2_session_callbacks_set_on_header_callback(cbs, proxy_h2_on_header); + + /* The nghttp2 session is not yet setup, do it */ + rc = proxy_h2_client_new(cf, cbs); + if(rc) { + failf(data, "Couldn't initialize nghttp2"); + goto out; + } + + { + nghttp2_settings_entry iv[3]; + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = Curl_multi_max_concurrent_streams(data->multi); + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = H2_TUNNEL_WINDOW_SIZE; + iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[2].value = 0; + rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, iv, 3); + if(rc) { + failf(data, "nghttp2_submit_settings() failed: %s(%d)", + nghttp2_strerror(rc), rc); + result = CURLE_HTTP2; + goto out; + } + } + + rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0, + PROXY_HTTP2_HUGE_WINDOW_SIZE); + if(rc) { + failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", + nghttp2_strerror(rc), rc); + result = CURLE_HTTP2; + goto out; + } + + + /* all set, traffic will be send on connect */ + result = CURLE_OK; + +out: + if(cbs) + nghttp2_session_callbacks_del(cbs); + CURL_TRC_CF(data, cf, "[0] init proxy ctx -> %d", result); + return result; +} + +static int proxy_h2_should_close_session(struct cf_h2_proxy_ctx *ctx) +{ + return !nghttp2_session_want_read(ctx->h2) && + !nghttp2_session_want_write(ctx->h2); +} + +static CURLcode proxy_h2_nw_out_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + ssize_t nwritten; + CURLcode result; + + (void)data; + if(Curl_bufq_is_empty(&ctx->outbufq)) + return CURLE_OK; + + nwritten = Curl_bufq_pass(&ctx->outbufq, proxy_h2_nw_out_writer, cf, + &result); + if(nwritten < 0) { + if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "[0] flush nw send buffer(%zu) -> EAGAIN", + Curl_bufq_len(&ctx->outbufq)); + ctx->nw_out_blocked = 1; + } + return result; + } + CURL_TRC_CF(data, cf, "[0] nw send buffer flushed"); + return Curl_bufq_is_empty(&ctx->outbufq)? CURLE_OK: CURLE_AGAIN; +} + +/* + * Processes pending input left in network input buffer. + * This function returns 0 if it succeeds, or -1 and error code will + * be assigned to *err. + */ +static int proxy_h2_process_pending_input(struct Curl_cfilter *cf, + struct Curl_easy *data, + CURLcode *err) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + const unsigned char *buf; + size_t blen; + ssize_t rv; + + while(Curl_bufq_peek(&ctx->inbufq, &buf, &blen)) { + + rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen); + CURL_TRC_CF(data, cf, "[0] %zu bytes to nghttp2 -> %zd", blen, rv); + if(rv < 0) { + failf(data, + "process_pending_input: nghttp2_session_mem_recv() returned " + "%zd:%s", rv, nghttp2_strerror((int)rv)); + *err = CURLE_RECV_ERROR; + return -1; + } + Curl_bufq_skip(&ctx->inbufq, (size_t)rv); + if(Curl_bufq_is_empty(&ctx->inbufq)) { + CURL_TRC_CF(data, cf, "[0] all data in connection buffer processed"); + break; + } + else { + CURL_TRC_CF(data, cf, "[0] process_pending_input: %zu bytes left " + "in connection buffer", Curl_bufq_len(&ctx->inbufq)); + } + } + + return 0; +} + +static CURLcode proxy_h2_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + ssize_t nread; + + /* Process network input buffer fist */ + if(!Curl_bufq_is_empty(&ctx->inbufq)) { + CURL_TRC_CF(data, cf, "[0] process %zu bytes in connection buffer", + Curl_bufq_len(&ctx->inbufq)); + if(proxy_h2_process_pending_input(cf, data, &result) < 0) + return result; + } + + /* Receive data from the "lower" filters, e.g. network until + * it is time to stop or we have enough data for this stream */ + while(!ctx->conn_closed && /* not closed the connection */ + !ctx->tunnel.closed && /* nor the tunnel */ + Curl_bufq_is_empty(&ctx->inbufq) && /* and we consumed our input */ + !Curl_bufq_is_full(&ctx->tunnel.recvbuf)) { + + nread = Curl_bufq_slurp(&ctx->inbufq, proxy_nw_in_reader, cf, &result); + CURL_TRC_CF(data, cf, "[0] read %zu bytes nw data -> %zd, %d", + Curl_bufq_len(&ctx->inbufq), nread, result); + if(nread < 0) { + if(result != CURLE_AGAIN) { + failf(data, "Failed receiving HTTP2 data"); + return result; + } + break; + } + else if(nread == 0) { + ctx->conn_closed = TRUE; + break; + } + + if(proxy_h2_process_pending_input(cf, data, &result)) + return result; + } + + if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { + connclose(cf->conn, "GOAWAY received"); + } + + return CURLE_OK; +} + +static CURLcode proxy_h2_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + int rv = 0; + + ctx->nw_out_blocked = 0; + while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2)) + rv = nghttp2_session_send(ctx->h2); + + if(nghttp2_is_fatal(rv)) { + CURL_TRC_CF(data, cf, "[0] nghttp2_session_send error (%s)%d", + nghttp2_strerror(rv), rv); + return CURLE_SEND_ERROR; + } + return proxy_h2_nw_out_flush(cf, data); +} + +static ssize_t on_session_send(nghttp2_session *h2, + const uint8_t *buf, size_t blen, int flags, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nwritten; + CURLcode result = CURLE_OK; + + (void)h2; + (void)flags; + DEBUGASSERT(data); + + nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen, + proxy_h2_nw_out_writer, cf, &result); + if(nwritten < 0) { + if(result == CURLE_AGAIN) { + return NGHTTP2_ERR_WOULDBLOCK; + } + failf(data, "Failed sending HTTP2 data"); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + if(!nwritten) + return NGHTTP2_ERR_WOULDBLOCK; + + return nwritten; +} + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static int proxy_h2_fr_print(const nghttp2_frame *frame, + char *buffer, size_t blen) +{ + switch(frame->hd.type) { + case NGHTTP2_DATA: { + return msnprintf(buffer, blen, + "FRAME[DATA, len=%d, eos=%d, padlen=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM), + (int)frame->data.padlen); + } + case NGHTTP2_HEADERS: { + return msnprintf(buffer, blen, + "FRAME[HEADERS, len=%d, hend=%d, eos=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS), + !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)); + } + case NGHTTP2_PRIORITY: { + return msnprintf(buffer, blen, + "FRAME[PRIORITY, len=%d, flags=%d]", + (int)frame->hd.length, frame->hd.flags); + } + case NGHTTP2_RST_STREAM: { + return msnprintf(buffer, blen, + "FRAME[RST_STREAM, len=%d, flags=%d, error=%u]", + (int)frame->hd.length, frame->hd.flags, + frame->rst_stream.error_code); + } + case NGHTTP2_SETTINGS: { + if(frame->hd.flags & NGHTTP2_FLAG_ACK) { + return msnprintf(buffer, blen, "FRAME[SETTINGS, ack=1]"); + } + return msnprintf(buffer, blen, + "FRAME[SETTINGS, len=%d]", (int)frame->hd.length); + } + case NGHTTP2_PUSH_PROMISE: { + return msnprintf(buffer, blen, + "FRAME[PUSH_PROMISE, len=%d, hend=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS)); + } + case NGHTTP2_PING: { + return msnprintf(buffer, blen, + "FRAME[PING, len=%d, ack=%d]", + (int)frame->hd.length, + frame->hd.flags&NGHTTP2_FLAG_ACK); + } + case NGHTTP2_GOAWAY: { + char scratch[128]; + size_t s_len = sizeof(scratch)/sizeof(scratch[0]); + size_t len = (frame->goaway.opaque_data_len < s_len)? + frame->goaway.opaque_data_len : s_len-1; + if(len) + memcpy(scratch, frame->goaway.opaque_data, len); + scratch[len] = '\0'; + return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', " + "last_stream=%d]", frame->goaway.error_code, + scratch, frame->goaway.last_stream_id); + } + case NGHTTP2_WINDOW_UPDATE: { + return msnprintf(buffer, blen, + "FRAME[WINDOW_UPDATE, incr=%d]", + frame->window_update.window_size_increment); + } + default: + return msnprintf(buffer, blen, "FRAME[%d, len=%d, flags=%d]", + frame->hd.type, (int)frame->hd.length, + frame->hd.flags); + } +} + +static int proxy_h2_on_frame_send(nghttp2_session *session, + const nghttp2_frame *frame, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + (void)session; + DEBUGASSERT(data); + if(data && Curl_trc_cf_is_verbose(cf, data)) { + char buffer[256]; + int len; + len = proxy_h2_fr_print(frame, buffer, sizeof(buffer)-1); + buffer[len] = 0; + CURL_TRC_CF(data, cf, "[%d] -> %s", frame->hd.stream_id, buffer); + } + return 0; +} +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + +static int proxy_h2_on_frame_recv(nghttp2_session *session, + const nghttp2_frame *frame, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + int32_t stream_id = frame->hd.stream_id; + + (void)session; + DEBUGASSERT(data); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(Curl_trc_cf_is_verbose(cf, data)) { + char buffer[256]; + int len; + len = proxy_h2_fr_print(frame, buffer, sizeof(buffer)-1); + buffer[len] = 0; + CURL_TRC_CF(data, cf, "[%d] <- %s",frame->hd.stream_id, buffer); + } +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + + if(!stream_id) { + /* stream ID zero is for connection-oriented stuff */ + DEBUGASSERT(data); + switch(frame->hd.type) { + case NGHTTP2_SETTINGS: + /* Since the initial stream window is 64K, a request might be on HOLD, + * due to exhaustion. The (initial) SETTINGS may announce a much larger + * window and *assume* that we treat this like a WINDOW_UPDATE. Some + * servers send an explicit WINDOW_UPDATE, but not all seem to do that. + * To be safe, we UNHOLD a stream in order not to stall. */ + if(CURL_WANT_SEND(data)) { + drain_tunnel(cf, data, &ctx->tunnel); + } + break; + case NGHTTP2_GOAWAY: + ctx->goaway = TRUE; + break; + default: + break; + } + return 0; + } + + if(stream_id != ctx->tunnel.stream_id) { + CURL_TRC_CF(data, cf, "[%d] rcvd FRAME not for tunnel", stream_id); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + switch(frame->hd.type) { + case NGHTTP2_HEADERS: + /* nghttp2 guarantees that :status is received, and we store it to + stream->status_code. Fuzzing has proven this can still be reached + without status code having been set. */ + if(!ctx->tunnel.resp) + return NGHTTP2_ERR_CALLBACK_FAILURE; + /* Only final status code signals the end of header */ + CURL_TRC_CF(data, cf, "[%d] got http status: %d", + stream_id, ctx->tunnel.resp->status); + if(!ctx->tunnel.has_final_response) { + if(ctx->tunnel.resp->status / 100 != 1) { + ctx->tunnel.has_final_response = TRUE; + } + } + break; + case NGHTTP2_WINDOW_UPDATE: + if(CURL_WANT_SEND(data)) { + drain_tunnel(cf, data, &ctx->tunnel); + } + break; + default: + break; + } + return 0; +} + +static int proxy_h2_on_header(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + int32_t stream_id = frame->hd.stream_id; + CURLcode result; + + (void)flags; + (void)data; + (void)session; + DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ + if(stream_id != ctx->tunnel.stream_id) { + CURL_TRC_CF(data, cf, "[%d] header for non-tunnel stream: " + "%.*s: %.*s", stream_id, + (int)namelen, name, (int)valuelen, value); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + if(frame->hd.type == NGHTTP2_PUSH_PROMISE) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + if(ctx->tunnel.has_final_response) { + /* we do not do anything with trailers for tunnel streams */ + return 0; + } + + if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 && + memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) { + int http_status; + struct http_resp *resp; + + /* status: always comes first, we might get more than one response, + * link the previous ones for keepers */ + result = Curl_http_decode_status(&http_status, + (const char *)value, valuelen); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + result = Curl_http_resp_make(&resp, http_status, NULL); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + resp->prev = ctx->tunnel.resp; + ctx->tunnel.resp = resp; + CURL_TRC_CF(data, cf, "[%d] status: HTTP/2 %03d", + stream_id, ctx->tunnel.resp->status); + return 0; + } + + if(!ctx->tunnel.resp) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + result = Curl_dynhds_add(&ctx->tunnel.resp->headers, + (const char *)name, namelen, + (const char *)value, valuelen); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + CURL_TRC_CF(data, cf, "[%d] header: %.*s: %.*s", + stream_id, (int)namelen, name, (int)valuelen, value); + + return 0; /* 0 is successful */ +} + +static ssize_t tunnel_send_callback(nghttp2_session *session, + int32_t stream_id, + uint8_t *buf, size_t length, + uint32_t *data_flags, + nghttp2_data_source *source, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct tunnel_stream *ts; + CURLcode result; + ssize_t nread; + + (void)source; + (void)data; + (void)ctx; + + if(!stream_id) + return NGHTTP2_ERR_INVALID_ARGUMENT; + + ts = nghttp2_session_get_stream_user_data(session, stream_id); + if(!ts) + return NGHTTP2_ERR_CALLBACK_FAILURE; + DEBUGASSERT(ts == &ctx->tunnel); + + nread = Curl_bufq_read(&ts->sendbuf, buf, length, &result); + if(nread < 0) { + if(result != CURLE_AGAIN) + return NGHTTP2_ERR_CALLBACK_FAILURE; + return NGHTTP2_ERR_DEFERRED; + } + if(ts->closed && Curl_bufq_is_empty(&ts->sendbuf)) + *data_flags = NGHTTP2_DATA_FLAG_EOF; + + CURL_TRC_CF(data, cf, "[%d] tunnel_send_callback -> %zd", + ts->stream_id, nread); + return nread; +} + +static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const uint8_t *mem, size_t len, void *userp) +{ + struct Curl_cfilter *cf = userp; + struct cf_h2_proxy_ctx *ctx = cf->ctx; + ssize_t nwritten; + CURLcode result; + + (void)flags; + (void)session; + DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ + + if(stream_id != ctx->tunnel.stream_id) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + nwritten = Curl_bufq_write(&ctx->tunnel.recvbuf, mem, len, &result); + if(nwritten < 0) { + if(result != CURLE_AGAIN) + return NGHTTP2_ERR_CALLBACK_FAILURE; + nwritten = 0; + } + DEBUGASSERT((size_t)nwritten == len); + return 0; +} + +static int proxy_h2_on_stream_close(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, void *userp) +{ + struct Curl_cfilter *cf = userp; + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + (void)session; + (void)data; + + if(stream_id != ctx->tunnel.stream_id) + return 0; + + CURL_TRC_CF(data, cf, "[%d] proxy_h2_on_stream_close, %s (err %d)", + stream_id, nghttp2_http2_strerror(error_code), error_code); + ctx->tunnel.closed = TRUE; + ctx->tunnel.error = error_code; + + return 0; +} + +static CURLcode proxy_h2_submit(int32_t *pstream_id, + struct Curl_cfilter *cf, + struct Curl_easy *data, + nghttp2_session *h2, + struct httpreq *req, + const nghttp2_priority_spec *pri_spec, + void *stream_user_data, + nghttp2_data_source_read_callback read_callback, + void *read_ctx) +{ + struct dynhds h2_headers; + nghttp2_nv *nva = NULL; + int32_t stream_id = -1; + size_t nheader; + CURLcode result; + + (void)cf; + Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + result = Curl_http_req_to_h2(&h2_headers, req, data); + if(result) + goto out; + + nva = Curl_dynhds_to_nva(&h2_headers, &nheader); + if(!nva) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(read_callback) { + nghttp2_data_provider data_prd; + + data_prd.read_callback = read_callback; + data_prd.source.ptr = read_ctx; + stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader, + &data_prd, stream_user_data); + } + else { + stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader, + NULL, stream_user_data); + } + + if(stream_id < 0) { + failf(data, "nghttp2_session_upgrade2() failed: %s(%d)", + nghttp2_strerror(stream_id), stream_id); + result = CURLE_SEND_ERROR; + goto out; + } + result = CURLE_OK; + +out: + free(nva); + Curl_dynhds_free(&h2_headers); + *pstream_id = stream_id; + return result; +} + +static CURLcode submit_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct tunnel_stream *ts) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + CURLcode result; + struct httpreq *req = NULL; + + result = Curl_http_proxy_create_CONNECT(&req, cf, data, 2); + if(result) + goto out; + + infof(data, "Establish HTTP/2 proxy tunnel to %s", req->authority); + + result = proxy_h2_submit(&ts->stream_id, cf, data, ctx->h2, req, + NULL, ts, tunnel_send_callback, cf); + if(result) { + CURL_TRC_CF(data, cf, "[%d] send, nghttp2_submit_request error: %s", + ts->stream_id, nghttp2_strerror(ts->stream_id)); + } + +out: + if(req) + Curl_http_req_free(req); + if(result) + failf(data, "Failed sending CONNECT to proxy"); + return result; +} + +static CURLcode inspect_response(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct tunnel_stream *ts) +{ + CURLcode result = CURLE_OK; + struct dynhds_entry *auth_reply = NULL; + (void)cf; + + DEBUGASSERT(ts->resp); + if(ts->resp->status/100 == 2) { + infof(data, "CONNECT tunnel established, response %d", ts->resp->status); + h2_tunnel_go_state(cf, ts, H2_TUNNEL_ESTABLISHED, data); + return CURLE_OK; + } + + if(ts->resp->status == 401) { + auth_reply = Curl_dynhds_cget(&ts->resp->headers, "WWW-Authenticate"); + } + else if(ts->resp->status == 407) { + auth_reply = Curl_dynhds_cget(&ts->resp->headers, "Proxy-Authenticate"); + } + + if(auth_reply) { + CURL_TRC_CF(data, cf, "[0] CONNECT: fwd auth header '%s'", + auth_reply->value); + result = Curl_http_input_auth(data, ts->resp->status == 407, + auth_reply->value); + if(result) + return result; + if(data->req.newurl) { + /* Indicator that we should try again */ + Curl_safefree(data->req.newurl); + h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data); + return CURLE_OK; + } + } + + /* Seems to have failed */ + return CURLE_RECV_ERROR; +} + +static CURLcode H2_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct tunnel_stream *ts) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + DEBUGASSERT(ts); + DEBUGASSERT(ts->authority); + do { + switch(ts->state) { + case H2_TUNNEL_INIT: + /* Prepare the CONNECT request and make a first attempt to send. */ + CURL_TRC_CF(data, cf, "[0] CONNECT start for %s", ts->authority); + result = submit_CONNECT(cf, data, ts); + if(result) + goto out; + h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data); + /* FALLTHROUGH */ + + case H2_TUNNEL_CONNECT: + /* see that the request is completely sent */ + result = proxy_h2_progress_ingress(cf, data); + if(!result) + result = proxy_h2_progress_egress(cf, data); + if(result && result != CURLE_AGAIN) { + h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data); + break; + } + + if(ts->has_final_response) { + h2_tunnel_go_state(cf, ts, H2_TUNNEL_RESPONSE, data); + } + else { + result = CURLE_OK; + goto out; + } + /* FALLTHROUGH */ + + case H2_TUNNEL_RESPONSE: + DEBUGASSERT(ts->has_final_response); + result = inspect_response(cf, data, ts); + if(result) + goto out; + break; + + case H2_TUNNEL_ESTABLISHED: + return CURLE_OK; + + case H2_TUNNEL_FAILED: + return CURLE_RECV_ERROR; + + default: + break; + } + + } while(ts->state == H2_TUNNEL_INIT); + +out: + if(result || ctx->tunnel.closed) + h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data); + return result; +} + +static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + timediff_t check; + struct tunnel_stream *ts = &ctx->tunnel; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* Connect the lower filters first */ + if(!cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + } + + *done = FALSE; + + CF_DATA_SAVE(save, cf, data); + if(!ctx->h2) { + result = cf_h2_proxy_ctx_init(cf, data); + if(result) + goto out; + } + DEBUGASSERT(ts->authority); + + check = Curl_timeleft(data, NULL, TRUE); + if(check <= 0) { + failf(data, "Proxy CONNECT aborted due to timeout"); + result = CURLE_OPERATION_TIMEDOUT; + goto out; + } + + /* for the secondary socket (FTP), use the "connect to host" + * but ignore the "connect to port" (use the secondary port) + */ + result = H2_CONNECT(cf, data, ts); + +out: + *done = (result == CURLE_OK) && (ts->state == H2_TUNNEL_ESTABLISHED); + cf->connected = *done; + CF_DATA_RESTORE(cf, save); + return result; +} + +static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + + if(ctx) { + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + cf_h2_proxy_ctx_clear(ctx); + CF_DATA_RESTORE(cf, save); + } + if(cf->next) + cf->next->cft->do_close(cf->next, data); +} + +static void cf_h2_proxy_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + + (void)data; + if(ctx) { + cf_h2_proxy_ctx_free(ctx); + cf->ctx = NULL; + } +} + +static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + if((ctx && !Curl_bufq_is_empty(&ctx->inbufq)) || + (ctx && ctx->tunnel.state == H2_TUNNEL_ESTABLISHED && + !Curl_bufq_is_empty(&ctx->tunnel.recvbuf))) + return TRUE; + return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE; +} + +static void cf_h2_proxy_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); + bool want_recv, want_send; + + Curl_pollset_check(data, ps, sock, &want_recv, &want_send); + if(ctx->h2 && (want_recv || want_send)) { + struct cf_call_data save; + bool c_exhaust, s_exhaust; + + CF_DATA_SAVE(save, cf, data); + c_exhaust = !nghttp2_session_get_remote_window_size(ctx->h2); + s_exhaust = ctx->tunnel.stream_id >= 0 && + !nghttp2_session_get_stream_remote_window_size( + ctx->h2, ctx->tunnel.stream_id); + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + (!c_exhaust && nghttp2_session_want_write(ctx->h2)); + + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } +} + +static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf, + struct Curl_easy *data, + CURLcode *err) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + ssize_t rv = 0; + + if(ctx->tunnel.error == NGHTTP2_REFUSED_STREAM) { + CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new " + "connection", ctx->tunnel.stream_id); + connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */ + *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ + return -1; + } + else if(ctx->tunnel.error != NGHTTP2_NO_ERROR) { + failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)", + ctx->tunnel.stream_id, nghttp2_http2_strerror(ctx->tunnel.error), + ctx->tunnel.error); + *err = CURLE_HTTP2_STREAM; + return -1; + } + else if(ctx->tunnel.reset) { + failf(data, "HTTP/2 stream %u was reset", ctx->tunnel.stream_id); + *err = CURLE_RECV_ERROR; + return -1; + } + + *err = CURLE_OK; + rv = 0; + CURL_TRC_CF(data, cf, "[%d] handle_tunnel_close -> %zd, %d", + ctx->tunnel.stream_id, rv, *err); + return rv; +} + +static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + ssize_t nread = -1; + + *err = CURLE_AGAIN; + if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) { + nread = Curl_bufq_read(&ctx->tunnel.recvbuf, + (unsigned char *)buf, len, err); + if(nread < 0) + goto out; + DEBUGASSERT(nread > 0); + } + + if(nread < 0) { + if(ctx->tunnel.closed) { + nread = h2_handle_tunnel_close(cf, data, err); + } + else if(ctx->tunnel.reset || + (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || + (ctx->goaway && ctx->last_stream_id < ctx->tunnel.stream_id)) { + *err = CURLE_RECV_ERROR; + nread = -1; + } + } + else if(nread == 0) { + *err = CURLE_AGAIN; + nread = -1; + } + +out: + CURL_TRC_CF(data, cf, "[%d] tunnel_recv(len=%zu) -> %zd, %d", + ctx->tunnel.stream_id, len, nread, *err); + return nread; +} + +static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + ssize_t nread = -1; + struct cf_call_data save; + CURLcode result; + + if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) { + *err = CURLE_RECV_ERROR; + return -1; + } + CF_DATA_SAVE(save, cf, data); + + if(Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) { + *err = proxy_h2_progress_ingress(cf, data); + if(*err) + goto out; + } + + nread = tunnel_recv(cf, data, buf, len, err); + + if(nread > 0) { + CURL_TRC_CF(data, cf, "[%d] increase window by %zd", + ctx->tunnel.stream_id, nread); + nghttp2_session_consume(ctx->h2, ctx->tunnel.stream_id, (size_t)nread); + } + + result = proxy_h2_progress_egress(cf, data); + if(result == CURLE_AGAIN) { + /* pending data to send, need to be called again. Ideally, we'd + * monitor the socket for POLLOUT, but we might not be in SENDING + * transfer state any longer and are unable to make this happen. + */ + CURL_TRC_CF(data, cf, "[%d] egress blocked, DRAIN", + ctx->tunnel.stream_id); + drain_tunnel(cf, data, &ctx->tunnel); + } + else if(result) { + *err = result; + nread = -1; + } + +out: + if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) && + (nread >= 0 || *err == CURLE_AGAIN)) { + /* data pending and no fatal error to report. Need to trigger + * draining to avoid stalling when no socket events happen. */ + drain_tunnel(cf, data, &ctx->tunnel); + } + CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d", + ctx->tunnel.stream_id, len, nread, *err); + CF_DATA_RESTORE(cf, save); + return nread; +} + +static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct cf_call_data save; + int rv; + ssize_t nwritten; + CURLcode result; + int blocked = 0; + + if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) { + *err = CURLE_SEND_ERROR; + return -1; + } + CF_DATA_SAVE(save, cf, data); + + if(ctx->tunnel.closed) { + nwritten = -1; + *err = CURLE_SEND_ERROR; + goto out; + } + else if(ctx->tunnel.upload_blocked_len) { + /* the data in `buf` has already been submitted or added to the + * buffers, but have been EAGAINed on the last invocation. */ + DEBUGASSERT(len >= ctx->tunnel.upload_blocked_len); + if(len < ctx->tunnel.upload_blocked_len) { + /* Did we get called again with a smaller `len`? This should not + * happen. We are not prepared to handle that. */ + failf(data, "HTTP/2 proxy, send again with decreased length"); + *err = CURLE_HTTP2; + nwritten = -1; + goto out; + } + nwritten = (ssize_t)ctx->tunnel.upload_blocked_len; + ctx->tunnel.upload_blocked_len = 0; + *err = CURLE_OK; + } + else { + nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err); + if(nwritten < 0) { + if(*err != CURLE_AGAIN) + goto out; + nwritten = 0; + } + } + + if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) { + /* req body data is buffered, resume the potentially suspended stream */ + rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id); + if(nghttp2_is_fatal(rv)) { + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + } + + result = proxy_h2_progress_ingress(cf, data); + if(result) { + *err = result; + nwritten = -1; + goto out; + } + + /* Call the nghttp2 send loop and flush to write ALL buffered data, + * headers and/or request body completely out to the network */ + result = proxy_h2_progress_egress(cf, data); + if(result == CURLE_AGAIN) { + blocked = 1; + } + else if(result) { + *err = result; + nwritten = -1; + goto out; + } + else if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) { + /* although we wrote everything that nghttp2 wants to send now, + * there is data left in our stream send buffer unwritten. This may + * be due to the stream's HTTP/2 flow window being exhausted. */ + blocked = 1; + } + + if(blocked) { + /* Unable to send all data, due to connection blocked or H2 window + * exhaustion. Data is left in our stream buffer, or nghttp2's internal + * frame buffer or our network out buffer. */ + size_t rwin = nghttp2_session_get_stream_remote_window_size( + ctx->h2, ctx->tunnel.stream_id); + if(rwin == 0) { + /* H2 flow window exhaustion. + * FIXME: there is no way to HOLD all transfers that use this + * proxy connection AND to UNHOLD all of them again when the + * window increases. + * We *could* iterate over all data on this conn maybe? */ + CURL_TRC_CF(data, cf, "[%d] remote flow " + "window is exhausted", ctx->tunnel.stream_id); + } + + /* Whatever the cause, we need to return CURL_EAGAIN for this call. + * We have unwritten state that needs us being invoked again and EAGAIN + * is the only way to ensure that. */ + ctx->tunnel.upload_blocked_len = nwritten; + CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) BLOCK: win %u/%zu " + "blocked_len=%zu", + ctx->tunnel.stream_id, len, + nghttp2_session_get_remote_window_size(ctx->h2), rwin, + nwritten); + drain_tunnel(cf, data, &ctx->tunnel); + *err = CURLE_AGAIN; + nwritten = -1; + goto out; + } + else if(proxy_h2_should_close_session(ctx)) { + /* nghttp2 thinks this session is done. If the stream has not been + * closed, this is an error state for out transfer */ + if(ctx->tunnel.closed) { + *err = CURLE_SEND_ERROR; + nwritten = -1; + } + else { + CURL_TRC_CF(data, cf, "[0] send: nothing to do in this session"); + *err = CURLE_HTTP2; + nwritten = -1; + } + } + +out: + if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) && + (nwritten >= 0 || *err == CURLE_AGAIN)) { + /* data pending and no fatal error to report. Need to trigger + * draining to avoid stalling when no socket events happen. */ + drain_tunnel(cf, data, &ctx->tunnel); + } + CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %zd, %d, " + "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)", + ctx->tunnel.stream_id, len, nwritten, *err, + nghttp2_session_get_stream_remote_window_size( + ctx->h2, ctx->tunnel.stream_id), + nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&ctx->tunnel.sendbuf), + Curl_bufq_len(&ctx->outbufq)); + CF_DATA_RESTORE(cf, save); + return nwritten; +} + +static bool proxy_h2_connisalive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + bool alive = TRUE; + + *input_pending = FALSE; + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) + return FALSE; + + if(*input_pending) { + /* This happens before we've sent off a request and the connection is + not in use by any other transfer, there shouldn't be any data here, + only "protocol frames" */ + CURLcode result; + ssize_t nread = -1; + + *input_pending = FALSE; + nread = Curl_bufq_slurp(&ctx->inbufq, proxy_nw_in_reader, cf, &result); + if(nread != -1) { + if(proxy_h2_process_pending_input(cf, data, &result) < 0) + /* immediate error, considered dead */ + alive = FALSE; + else { + alive = !proxy_h2_should_close_session(ctx); + } + } + else if(result != CURLE_AGAIN) { + /* the read failed so let's say this is dead anyway */ + alive = FALSE; + } + } + + return alive; +} + +static bool cf_h2_proxy_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + CURLcode result; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + result = (ctx && ctx->h2 && proxy_h2_connisalive(cf, data, input_pending)); + CURL_TRC_CF(data, cf, "[0] conn alive -> %d, input_pending=%d", + result, *input_pending); + CF_DATA_RESTORE(cf, save); + return result; +} + +struct Curl_cftype Curl_cft_h2_proxy = { + "H2-PROXY", + CF_TYPE_IP_CONNECT, + CURL_LOG_LVL_NONE, + cf_h2_proxy_destroy, + cf_h2_proxy_connect, + cf_h2_proxy_close, + Curl_cf_http_proxy_get_host, + cf_h2_proxy_adjust_pollset, + cf_h2_proxy_data_pending, + cf_h2_proxy_send, + cf_h2_proxy_recv, + Curl_cf_def_cntrl, + cf_h2_proxy_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf_h2_proxy = NULL; + struct cf_h2_proxy_ctx *ctx; + CURLcode result = CURLE_OUT_OF_MEMORY; + + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) + goto out; + + result = Curl_cf_create(&cf_h2_proxy, &Curl_cft_h2_proxy, ctx); + if(result) + goto out; + + Curl_conn_cf_insert_after(cf, cf_h2_proxy); + result = CURLE_OK; + +out: + if(result) + cf_h2_proxy_ctx_free(ctx); + return result; +} + +#endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */ diff --git a/Utilities/cmcurl/lib/cf-h2-proxy.h b/Utilities/cmcurl/lib/cf-h2-proxy.h new file mode 100644 index 0000000..c01bf62 --- /dev/null +++ b/Utilities/cmcurl/lib/cf-h2-proxy.h @@ -0,0 +1,39 @@ +#ifndef HEADER_CURL_H2_PROXY_H +#define HEADER_CURL_H2_PROXY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) + +CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, + struct Curl_easy *data); + +extern struct Curl_cftype Curl_cft_h2_proxy; + + +#endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */ + +#endif /* HEADER_CURL_H2_PROXY_H */ diff --git a/Utilities/cmcurl/lib/cf-haproxy.c b/Utilities/cmcurl/lib/cf-haproxy.c new file mode 100644 index 0000000..1ca4393 --- /dev/null +++ b/Utilities/cmcurl/lib/cf-haproxy.c @@ -0,0 +1,245 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_PROXY) + +#include <curl/curl.h> +#include "urldata.h" +#include "cfilters.h" +#include "cf-haproxy.h" +#include "curl_trc.h" +#include "multiif.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +typedef enum { + HAPROXY_INIT, /* init/default/no tunnel state */ + HAPROXY_SEND, /* data_out being sent */ + HAPROXY_DONE /* all work done */ +} haproxy_state; + +struct cf_haproxy_ctx { + int state; + struct dynbuf data_out; +}; + +static void cf_haproxy_ctx_reset(struct cf_haproxy_ctx *ctx) +{ + DEBUGASSERT(ctx); + ctx->state = HAPROXY_INIT; + Curl_dyn_reset(&ctx->data_out); +} + +static void cf_haproxy_ctx_free(struct cf_haproxy_ctx *ctx) +{ + if(ctx) { + Curl_dyn_free(&ctx->data_out); + free(ctx); + } +} + +static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf, + struct Curl_easy *data) +{ + struct cf_haproxy_ctx *ctx = cf->ctx; + CURLcode result; + const char *tcp_version; + const char *client_ip; + + DEBUGASSERT(ctx); + DEBUGASSERT(ctx->state == HAPROXY_INIT); +#ifdef USE_UNIX_SOCKETS + if(cf->conn->unix_domain_socket) + /* the buffer is large enough to hold this! */ + result = Curl_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n")); + else { +#endif /* USE_UNIX_SOCKETS */ + /* Emit the correct prefix for IPv6 */ + tcp_version = cf->conn->bits.ipv6 ? "TCP6" : "TCP4"; + if(data->set.str[STRING_HAPROXY_CLIENT_IP]) + client_ip = data->set.str[STRING_HAPROXY_CLIENT_IP]; + else + client_ip = data->info.conn_local_ip; + + result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n", + tcp_version, + client_ip, + data->info.conn_primary_ip, + data->info.conn_local_port, + data->info.conn_primary_port); + +#ifdef USE_UNIX_SOCKETS + } +#endif /* USE_UNIX_SOCKETS */ + return result; +} + +static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_haproxy_ctx *ctx = cf->ctx; + CURLcode result; + size_t len; + + DEBUGASSERT(ctx); + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + result = cf->next->cft->do_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + + switch(ctx->state) { + case HAPROXY_INIT: + result = cf_haproxy_date_out_set(cf, data); + if(result) + goto out; + ctx->state = HAPROXY_SEND; + /* FALLTHROUGH */ + case HAPROXY_SEND: + len = Curl_dyn_len(&ctx->data_out); + if(len > 0) { + ssize_t written = Curl_conn_send(data, cf->sockindex, + Curl_dyn_ptr(&ctx->data_out), + len, &result); + if(written < 0) + goto out; + Curl_dyn_tail(&ctx->data_out, len - (size_t)written); + if(Curl_dyn_len(&ctx->data_out) > 0) { + result = CURLE_OK; + goto out; + } + } + ctx->state = HAPROXY_DONE; + /* FALLTHROUGH */ + default: + Curl_dyn_free(&ctx->data_out); + break; + } + +out: + *done = (!result) && (ctx->state == HAPROXY_DONE); + cf->connected = *done; + return result; +} + +static void cf_haproxy_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)data; + CURL_TRC_CF(data, cf, "destroy"); + cf_haproxy_ctx_free(cf->ctx); +} + +static void cf_haproxy_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURL_TRC_CF(data, cf, "close"); + cf->connected = FALSE; + cf_haproxy_ctx_reset(cf->ctx); + if(cf->next) + cf->next->cft->do_close(cf->next, data); +} + +static void cf_haproxy_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + if(cf->next->connected && !cf->connected) { + /* If we are not connected, but the filter "below" is + * and not waiting on something, we are sending. */ + Curl_pollset_set_out_only(data, ps, Curl_conn_cf_get_socket(cf, data)); + } +} + +struct Curl_cftype Curl_cft_haproxy = { + "HAPROXY", + 0, + 0, + cf_haproxy_destroy, + cf_haproxy_connect, + cf_haproxy_close, + Curl_cf_def_get_host, + cf_haproxy_adjust_pollset, + Curl_cf_def_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf = NULL; + struct cf_haproxy_ctx *ctx; + CURLcode result; + + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->state = HAPROXY_INIT; + Curl_dyn_init(&ctx->data_out, DYN_HAXPROXY); + + result = Curl_cf_create(&cf, &Curl_cft_haproxy, ctx); + if(result) + goto out; + ctx = NULL; + +out: + cf_haproxy_ctx_free(ctx); + *pcf = result? NULL : cf; + return result; +} + +CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + CURLcode result; + + result = cf_haproxy_create(&cf, data); + if(result) + goto out; + Curl_conn_cf_insert_after(cf_at, cf); + +out: + return result; +} + +#endif /* !CURL_DISABLE_PROXY */ diff --git a/Utilities/cmcurl/lib/cf-haproxy.h b/Utilities/cmcurl/lib/cf-haproxy.h new file mode 100644 index 0000000..d02c323 --- /dev/null +++ b/Utilities/cmcurl/lib/cf-haproxy.h @@ -0,0 +1,39 @@ +#ifndef HEADER_CURL_CF_HAPROXY_H +#define HEADER_CURL_CF_HAPROXY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "urldata.h" + +#if !defined(CURL_DISABLE_PROXY) + +CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data); + +extern struct Curl_cftype Curl_cft_haproxy; + +#endif /* !CURL_DISABLE_PROXY */ + +#endif /* HEADER_CURL_CF_HAPROXY_H */ diff --git a/Utilities/cmcurl/lib/cf-https-connect.c b/Utilities/cmcurl/lib/cf-https-connect.c new file mode 100644 index 0000000..b4f33c8 --- /dev/null +++ b/Utilities/cmcurl/lib/cf-https-connect.c @@ -0,0 +1,531 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) + +#include "urldata.h" +#include <curl/curl.h> +#include "curl_trc.h" +#include "cfilters.h" +#include "connect.h" +#include "multiif.h" +#include "cf-https-connect.h" +#include "http2.h" +#include "vquic/vquic.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +typedef enum { + CF_HC_INIT, + CF_HC_CONNECT, + CF_HC_SUCCESS, + CF_HC_FAILURE +} cf_hc_state; + +struct cf_hc_baller { + const char *name; + struct Curl_cfilter *cf; + CURLcode result; + struct curltime started; + int reply_ms; + bool enabled; +}; + +static void cf_hc_baller_reset(struct cf_hc_baller *b, + struct Curl_easy *data) +{ + if(b->cf) { + Curl_conn_cf_close(b->cf, data); + Curl_conn_cf_discard_chain(&b->cf, data); + b->cf = NULL; + } + b->result = CURLE_OK; + b->reply_ms = -1; +} + +static bool cf_hc_baller_is_active(struct cf_hc_baller *b) +{ + return b->enabled && b->cf && !b->result; +} + +static bool cf_hc_baller_has_started(struct cf_hc_baller *b) +{ + return !!b->cf; +} + +static int cf_hc_baller_reply_ms(struct cf_hc_baller *b, + struct Curl_easy *data) +{ + if(b->reply_ms < 0) + b->cf->cft->query(b->cf, data, CF_QUERY_CONNECT_REPLY_MS, + &b->reply_ms, NULL); + return b->reply_ms; +} + +static bool cf_hc_baller_data_pending(struct cf_hc_baller *b, + const struct Curl_easy *data) +{ + return b->cf && !b->result && b->cf->cft->has_data_pending(b->cf, data); +} + +struct cf_hc_ctx { + cf_hc_state state; + const struct Curl_dns_entry *remotehost; + struct curltime started; /* when connect started */ + CURLcode result; /* overall result */ + struct cf_hc_baller h3_baller; + struct cf_hc_baller h21_baller; + int soft_eyeballs_timeout_ms; + int hard_eyeballs_timeout_ms; +}; + +static void cf_hc_baller_init(struct cf_hc_baller *b, + struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *name, + int transport) +{ + struct cf_hc_ctx *ctx = cf->ctx; + struct Curl_cfilter *save = cf->next; + + b->name = name; + cf->next = NULL; + b->started = Curl_now(); + b->result = Curl_cf_setup_insert_after(cf, data, ctx->remotehost, + transport, CURL_CF_SSL_ENABLE); + b->cf = cf->next; + cf->next = save; +} + +static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b, + struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + struct Curl_cfilter *save = cf->next; + + cf->next = b->cf; + b->result = Curl_conn_cf_connect(cf->next, data, FALSE, done); + b->cf = cf->next; /* it might mutate */ + cf->next = save; + return b->result; +} + +static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_hc_ctx *ctx = cf->ctx; + + if(ctx) { + cf_hc_baller_reset(&ctx->h3_baller, data); + cf_hc_baller_reset(&ctx->h21_baller, data); + ctx->state = CF_HC_INIT; + ctx->result = CURLE_OK; + ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout; + ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2; + } +} + +static CURLcode baller_connected(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_hc_baller *winner) +{ + struct cf_hc_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + DEBUGASSERT(winner->cf); + if(winner != &ctx->h3_baller) + cf_hc_baller_reset(&ctx->h3_baller, data); + if(winner != &ctx->h21_baller) + cf_hc_baller_reset(&ctx->h21_baller, data); + + CURL_TRC_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms", + winner->name, (int)Curl_timediff(Curl_now(), winner->started), + cf_hc_baller_reply_ms(winner, data)); + cf->next = winner->cf; + winner->cf = NULL; + + switch(cf->conn->alpn) { + case CURL_HTTP_VERSION_3: + infof(data, "using HTTP/3"); + break; + case CURL_HTTP_VERSION_2: +#ifdef USE_NGHTTP2 + /* Using nghttp2, we add the filter "below" us, so when the conn + * closes, we tear it down for a fresh reconnect */ + result = Curl_http2_switch_at(cf, data); + if(result) { + ctx->state = CF_HC_FAILURE; + ctx->result = result; + return result; + } +#endif + infof(data, "using HTTP/2"); + break; + default: + infof(data, "using HTTP/1.x"); + break; + } + ctx->state = CF_HC_SUCCESS; + cf->connected = TRUE; + Curl_conn_cf_cntrl(cf->next, data, TRUE, + CF_CTRL_CONN_INFO_UPDATE, 0, NULL); + return result; +} + + +static bool time_to_start_h21(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct curltime now) +{ + struct cf_hc_ctx *ctx = cf->ctx; + timediff_t elapsed_ms; + + if(!ctx->h21_baller.enabled || cf_hc_baller_has_started(&ctx->h21_baller)) + return FALSE; + + if(!ctx->h3_baller.enabled || !cf_hc_baller_is_active(&ctx->h3_baller)) + return TRUE; + + elapsed_ms = Curl_timediff(now, ctx->started); + if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) { + CURL_TRC_CF(data, cf, "hard timeout of %dms reached, starting h21", + ctx->hard_eyeballs_timeout_ms); + return TRUE; + } + + if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) { + if(cf_hc_baller_reply_ms(&ctx->h3_baller, data) < 0) { + CURL_TRC_CF(data, cf, "soft timeout of %dms reached, h3 has not " + "seen any data, starting h21", + ctx->soft_eyeballs_timeout_ms); + return TRUE; + } + /* set the effective hard timeout again */ + Curl_expire(data, ctx->hard_eyeballs_timeout_ms - elapsed_ms, + EXPIRE_ALPN_EYEBALLS); + } + return FALSE; +} + +static CURLcode cf_hc_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_hc_ctx *ctx = cf->ctx; + struct curltime now; + CURLcode result = CURLE_OK; + + (void)blocking; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; + now = Curl_now(); + switch(ctx->state) { + case CF_HC_INIT: + DEBUGASSERT(!ctx->h3_baller.cf); + DEBUGASSERT(!ctx->h21_baller.cf); + DEBUGASSERT(!cf->next); + CURL_TRC_CF(data, cf, "connect, init"); + ctx->started = now; + if(ctx->h3_baller.enabled) { + cf_hc_baller_init(&ctx->h3_baller, cf, data, "h3", TRNSPRT_QUIC); + if(ctx->h21_baller.enabled) + Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS); + } + else if(ctx->h21_baller.enabled) + cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", + cf->conn->transport); + ctx->state = CF_HC_CONNECT; + /* FALLTHROUGH */ + + case CF_HC_CONNECT: + if(cf_hc_baller_is_active(&ctx->h3_baller)) { + result = cf_hc_baller_connect(&ctx->h3_baller, cf, data, done); + if(!result && *done) { + result = baller_connected(cf, data, &ctx->h3_baller); + goto out; + } + } + + if(time_to_start_h21(cf, data, now)) { + cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", + cf->conn->transport); + } + + if(cf_hc_baller_is_active(&ctx->h21_baller)) { + CURL_TRC_CF(data, cf, "connect, check h21"); + result = cf_hc_baller_connect(&ctx->h21_baller, cf, data, done); + if(!result && *done) { + result = baller_connected(cf, data, &ctx->h21_baller); + goto out; + } + } + + if((!ctx->h3_baller.enabled || ctx->h3_baller.result) && + (!ctx->h21_baller.enabled || ctx->h21_baller.result)) { + /* both failed or disabled. we give up */ + CURL_TRC_CF(data, cf, "connect, all failed"); + result = ctx->result = ctx->h3_baller.enabled? + ctx->h3_baller.result : ctx->h21_baller.result; + ctx->state = CF_HC_FAILURE; + goto out; + } + result = CURLE_OK; + *done = FALSE; + break; + + case CF_HC_FAILURE: + result = ctx->result; + cf->connected = FALSE; + *done = FALSE; + break; + + case CF_HC_SUCCESS: + result = CURLE_OK; + cf->connected = TRUE; + *done = TRUE; + break; + } + +out: + CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); + return result; +} + +static void cf_hc_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + if(!cf->connected) { + struct cf_hc_ctx *ctx = cf->ctx; + struct cf_hc_baller *ballers[2]; + size_t i; + + ballers[0] = &ctx->h3_baller; + ballers[1] = &ctx->h21_baller; + for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { + struct cf_hc_baller *b = ballers[i]; + if(!cf_hc_baller_is_active(b)) + continue; + Curl_conn_cf_adjust_pollset(b->cf, data, ps); + } + CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num); + } +} + +static bool cf_hc_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_hc_ctx *ctx = cf->ctx; + + if(cf->connected) + return cf->next->cft->has_data_pending(cf->next, data); + + CURL_TRC_CF((struct Curl_easy *)data, cf, "data_pending"); + return cf_hc_baller_data_pending(&ctx->h3_baller, data) + || cf_hc_baller_data_pending(&ctx->h21_baller, data); +} + +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 Curl_cfilter *cfb; + struct curltime t, tmax; + + memset(&tmax, 0, sizeof(tmax)); + memset(&t, 0, sizeof(t)); + cfb = ctx->h21_baller.enabled? ctx->h21_baller.cf : NULL; + if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { + if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) + tmax = t; + } + memset(&t, 0, sizeof(t)); + cfb = ctx->h3_baller.enabled? ctx->h3_baller.cf : NULL; + if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { + if((t.tv_sec || t.tv_usec) && Curl_timediff_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) +{ + 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; + } + default: + break; + } + } + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + CURL_TRC_CF(data, cf, "close"); + cf_hc_reset(cf, data); + cf->connected = FALSE; + + if(cf->next) { + cf->next->cft->do_close(cf->next, data); + Curl_conn_cf_discard_chain(&cf->next, data); + } +} + +static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_hc_ctx *ctx = cf->ctx; + + (void)data; + CURL_TRC_CF(data, cf, "destroy"); + cf_hc_reset(cf, data); + Curl_safefree(ctx); +} + +struct Curl_cftype Curl_cft_http_connect = { + "HTTPS-CONNECT", + 0, + CURL_LOG_LVL_NONE, + cf_hc_destroy, + cf_hc_connect, + cf_hc_close, + Curl_cf_def_get_host, + cf_hc_adjust_pollset, + cf_hc_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_hc_query, +}; + +static CURLcode cf_hc_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + bool try_h3, bool try_h21) +{ + struct Curl_cfilter *cf = NULL; + struct cf_hc_ctx *ctx; + CURLcode result = CURLE_OK; + + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->remotehost = remotehost; + ctx->h3_baller.enabled = try_h3; + ctx->h21_baller.enabled = try_h21; + + result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx); + if(result) + goto out; + ctx = NULL; + cf_hc_reset(cf, data); + +out: + *pcf = result? NULL : cf; + free(ctx); + return result; +} + +static CURLcode cf_http_connect_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + bool try_h3, bool try_h21) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21); + if(result) + goto out; + Curl_conn_cf_add(data, conn, sockindex, cf); +out: + return result; +} + +CURLcode Curl_cf_https_setup(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost) +{ + bool try_h3 = FALSE, try_h21 = TRUE; /* defaults, for now */ + CURLcode result = CURLE_OK; + + (void)sockindex; + (void)remotehost; + + if(!conn->bits.tls_enable_alpn) + goto out; + + if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) { + result = Curl_conn_may_http3(data, conn); + if(result) /* can't do it */ + goto out; + try_h3 = TRUE; + try_h21 = FALSE; + } + else if(data->state.httpwant >= CURL_HTTP_VERSION_3) { + /* We assume that silently not even trying H3 is ok here */ + /* TODO: should we fail instead? */ + try_h3 = (Curl_conn_may_http3(data, conn) == CURLE_OK); + try_h21 = TRUE; + } + + result = cf_http_connect_add(data, conn, sockindex, remotehost, + try_h3, try_h21); +out: + return result; +} + +#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */ diff --git a/Utilities/cmcurl/lib/cf-https-connect.h b/Utilities/cmcurl/lib/cf-https-connect.h new file mode 100644 index 0000000..6a39527 --- /dev/null +++ b/Utilities/cmcurl/lib/cf-https-connect.h @@ -0,0 +1,58 @@ +#ifndef HEADER_CURL_CF_HTTP_H +#define HEADER_CURL_CF_HTTP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) + +struct Curl_cfilter; +struct Curl_easy; +struct connectdata; +struct Curl_cftype; +struct Curl_dns_entry; + +extern struct Curl_cftype Curl_cft_http_connect; + +CURLcode Curl_cf_http_connect_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + bool try_h3, bool try_h21); + +CURLcode +Curl_cf_http_connect_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + bool try_h3, bool try_h21); + + +CURLcode Curl_cf_https_setup(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost); + + +#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */ +#endif /* HEADER_CURL_CF_HTTP_H */ diff --git a/Utilities/cmcurl/lib/cf-socket.c b/Utilities/cmcurl/lib/cf-socket.c new file mode 100644 index 0000000..e42b4a8 --- /dev/null +++ b/Utilities/cmcurl/lib/cf-socket.c @@ -0,0 +1,1996 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> /* <netinet/tcp.h> may need it */ +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> /* for sockaddr_un */ +#endif +#ifdef HAVE_LINUX_TCP_H +#include <linux/tcp.h> +#elif defined(HAVE_NETINET_TCP_H) +#include <netinet/tcp.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include "urldata.h" +#include "bufq.h" +#include "sendf.h" +#include "if2ip.h" +#include "strerror.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "connect.h" +#include "select.h" +#include "url.h" /* for Curl_safefree() */ +#include "multiif.h" +#include "sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "inet_ntop.h" +#include "inet_pton.h" +#include "progress.h" +#include "warnless.h" +#include "conncache.h" +#include "multihandle.h" +#include "rand.h" +#include "share.h" +#include "version_win32.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY) && defined(_WIN32) +/* It makes support for IPv4-mapped IPv6 addresses. + * Linux kernel, NetBSD, FreeBSD and Darwin: default is off; + * Windows Vista and later: default is on; + * DragonFly BSD: acts like off, and dummy setting; + * OpenBSD and earlier Windows: unsupported. + * Linux: controlled by /proc/sys/net/ipv6/bindv6only. + */ +static void set_ipv6_v6only(curl_socket_t sockfd, int on) +{ + (void)setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)); +} +#else +#define set_ipv6_v6only(x,y) +#endif + +static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd) +{ +#if defined(TCP_NODELAY) + curl_socklen_t onoff = (curl_socklen_t) 1; + int level = IPPROTO_TCP; + char buffer[STRERROR_LEN]; + + if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff, + sizeof(onoff)) < 0) + infof(data, "Could not set TCP_NODELAY: %s", + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); +#else + (void)data; + (void)sockfd; +#endif +} + +#ifdef SO_NOSIGPIPE +/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when + sending data to a dead peer (instead of relying on the 4th argument to send + being MSG_NOSIGNAL). Possibly also existing and in use on other BSD + systems? */ +static void nosigpipe(struct Curl_easy *data, + curl_socket_t sockfd) +{ + int onoff = 1; + (void)data; + if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff, + sizeof(onoff)) < 0) { +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + char buffer[STRERROR_LEN]; + infof(data, "Could not set SO_NOSIGPIPE: %s", + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); +#endif + } +} +#else +#define nosigpipe(x,y) Curl_nop_stmt +#endif + +#if defined(__DragonFly__) || defined(HAVE_WINSOCK2_H) +/* DragonFlyBSD and Windows use millisecond units */ +#define KEEPALIVE_FACTOR(x) (x *= 1000) +#else +#define KEEPALIVE_FACTOR(x) +#endif + +#if defined(HAVE_WINSOCK2_H) && !defined(SIO_KEEPALIVE_VALS) +#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4) + +struct tcp_keepalive { + u_long onoff; + u_long keepalivetime; + u_long keepaliveinterval; +}; +#endif + +static void +tcpkeepalive(struct Curl_easy *data, + curl_socket_t sockfd) +{ + int optval = data->set.tcp_keepalive?1:0; + + /* only set IDLE and INTVL if setting KEEPALIVE is successful */ + if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set SO_KEEPALIVE on fd %d", sockfd); + } + else { +#if defined(SIO_KEEPALIVE_VALS) + struct tcp_keepalive vals; + DWORD dummy; + vals.onoff = 1; + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + vals.keepalivetime = optval; + optval = curlx_sltosi(data->set.tcp_keepintvl); + KEEPALIVE_FACTOR(optval); + vals.keepaliveinterval = optval; + if(WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, (LPVOID) &vals, sizeof(vals), + NULL, 0, &dummy, NULL, NULL) != 0) { + infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd %d: %d", + (int)sockfd, WSAGetLastError()); + } +#else +#ifdef TCP_KEEPIDLE + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPIDLE on fd %d", sockfd); + } +#elif defined(TCP_KEEPALIVE) + /* Mac OS X style */ + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPALIVE on fd %d", sockfd); + } +#endif +#ifdef TCP_KEEPINTVL + optval = curlx_sltosi(data->set.tcp_keepintvl); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPINTVL on fd %d", sockfd); + } +#endif +#endif + } +} + +/** + * Assign the address `ai` to the Curl_sockaddr_ex `dest` and + * set the transport used. + */ +void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, + const struct Curl_addrinfo *ai, + int transport) +{ + /* + * The Curl_sockaddr_ex structure is basically libcurl's external API + * curl_sockaddr structure with enough space available to directly hold + * any protocol-specific address structures. The variable declared here + * will be used to pass / receive data to/from the fopensocket callback + * if this has been set, before that, it is initialized from parameters. + */ + dest->family = ai->ai_family; + switch(transport) { + case TRNSPRT_TCP: + dest->socktype = SOCK_STREAM; + dest->protocol = IPPROTO_TCP; + break; + case TRNSPRT_UNIX: + dest->socktype = SOCK_STREAM; + dest->protocol = IPPROTO_IP; + break; + default: /* UDP and QUIC */ + dest->socktype = SOCK_DGRAM; + dest->protocol = IPPROTO_UDP; + break; + } + dest->addrlen = ai->ai_addrlen; + + if(dest->addrlen > sizeof(struct Curl_sockaddr_storage)) + dest->addrlen = sizeof(struct Curl_sockaddr_storage); + memcpy(&dest->sa_addr, ai->ai_addr, dest->addrlen); +} + +static CURLcode socket_open(struct Curl_easy *data, + struct Curl_sockaddr_ex *addr, + curl_socket_t *sockfd) +{ + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + if(data->set.fopensocket) { + /* + * If the opensocket callback is set, all the destination address + * information is passed to the callback. Depending on this information the + * callback may opt to abort the connection, this is indicated returning + * CURL_SOCKET_BAD; otherwise it will return a not-connected socket. When + * the callback returns a valid socket the destination address information + * might have been changed and this 'new' address will actually be used + * here to connect. + */ + Curl_set_in_callback(data, true); + *sockfd = data->set.fopensocket(data->set.opensocket_client, + CURLSOCKTYPE_IPCXN, + (struct curl_sockaddr *)addr); + Curl_set_in_callback(data, false); + } + else { + /* opensocket callback not set, so simply create the socket now */ + *sockfd = socket(addr->family, addr->socktype, addr->protocol); + } + + if(*sockfd == CURL_SOCKET_BAD) + /* no socket, no connection */ + return CURLE_COULDNT_CONNECT; + +#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) + if(data->conn->scope_id && (addr->family == AF_INET6)) { + struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr; + sa6->sin6_scope_id = data->conn->scope_id; + } +#endif + return CURLE_OK; +} + +/* + * Create a socket based on info from 'conn' and 'ai'. + * + * 'addr' should be a pointer to the correct struct to get data back, or NULL. + * 'sockfd' must be a pointer to a socket descriptor. + * + * If the open socket callback is set, used that! + * + */ +CURLcode Curl_socket_open(struct Curl_easy *data, + const struct Curl_addrinfo *ai, + struct Curl_sockaddr_ex *addr, + int transport, + curl_socket_t *sockfd) +{ + struct Curl_sockaddr_ex dummy; + + if(!addr) + /* if the caller doesn't want info back, use a local temp copy */ + addr = &dummy; + + Curl_sock_assign_addr(addr, ai, transport); + return socket_open(data, addr, sockfd); +} + +static int socket_close(struct Curl_easy *data, struct connectdata *conn, + int use_callback, curl_socket_t sock) +{ + if(use_callback && conn && conn->fclosesocket) { + int rc; + Curl_multi_closed(data, sock); + Curl_set_in_callback(data, true); + rc = conn->fclosesocket(conn->closesocket_client, sock); + Curl_set_in_callback(data, false); + return rc; + } + + if(conn) + /* tell the multi-socket code about this */ + Curl_multi_closed(data, sock); + + sclose(sock); + + return 0; +} + +/* + * Close a socket. + * + * 'conn' can be NULL, beware! + */ +int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t sock) +{ + return socket_close(data, conn, FALSE, sock); +} + +#ifdef USE_WINSOCK +/* When you run a program that uses the Windows Sockets API, you may + experience slow performance when you copy data to a TCP server. + + https://support.microsoft.com/kb/823764 + + Work-around: Make the Socket Send Buffer Size Larger Than the Program Send + Buffer Size + + The problem described in this knowledge-base is applied only to pre-Vista + Windows. Following function trying to detect OS version and skips + SO_SNDBUF adjustment for Windows Vista and above. +*/ +#define DETECT_OS_NONE 0 +#define DETECT_OS_PREVISTA 1 +#define DETECT_OS_VISTA_OR_LATER 2 + +void Curl_sndbufset(curl_socket_t sockfd) +{ + int val = CURL_MAX_WRITE_SIZE + 32; + int curval = 0; + int curlen = sizeof(curval); + + static int detectOsState = DETECT_OS_NONE; + + if(detectOsState == DETECT_OS_NONE) { + if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) + detectOsState = DETECT_OS_VISTA_OR_LATER; + else + detectOsState = DETECT_OS_PREVISTA; + } + + if(detectOsState == DETECT_OS_VISTA_OR_LATER) + return; + + if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0) + if(curval > val) + return; + + setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val)); +} +#endif + +#ifndef CURL_DISABLE_BINDLOCAL +static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t sockfd, int af, unsigned int scope) +{ + struct Curl_sockaddr_storage sa; + struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ + curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */ + struct sockaddr_in *si4 = (struct sockaddr_in *)&sa; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; +#endif + + struct Curl_dns_entry *h = NULL; + unsigned short port = data->set.localport; /* use this port number, 0 for + "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]; + int error; +#ifdef IP_BIND_ADDRESS_NO_PORT + int on = 1; +#endif +#ifndef ENABLE_IPV6 + (void)scope; +#endif + + /************************************************************* + * Select device to bind socket to + *************************************************************/ + if(!dev && !port) + /* no local kind of binding was requested */ + return CURLE_OK; + + memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); + + if(dev && (strlen(dev)<255) ) { + char myhost[256] = ""; + int done = 0; /* -1 for error, 1 for address found */ + bool is_interface = FALSE; + bool is_host = FALSE; + static const char *if_prefix = "if!"; + static const char *host_prefix = "host!"; + + if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) { + dev += strlen(if_prefix); + is_interface = TRUE; + } + else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) { + dev += strlen(host_prefix); + is_host = TRUE; + } + + /* interface */ + if(!is_host) { +#ifdef SO_BINDTODEVICE + /* + * This binds the local socket to a particular interface. This will + * force even requests to other local interfaces to go out the external + * interface. Only bind to the interface when specified as interface, + * not just as a hostname or ip address. + * + * The interface might be a VRF, eg: vrf-blue, which means it cannot be + * converted to an IP address and would fail Curl_if2ip. Simply try to + * use it straight away. + */ + if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, + dev, (curl_socklen_t)strlen(dev) + 1) == 0) { + /* This is often "errno 1, error: Operation not permitted" if you're + * not running as root or another suitable privileged user. If it + * succeeds it means the parameter was a valid interface and not an IP + * address. Return immediately. + */ + infof(data, "socket successfully bound to interface '%s'", dev); + return CURLE_OK; + } +#endif + + switch(Curl_if2ip(af, +#ifdef ENABLE_IPV6 + scope, conn->scope_id, +#endif + dev, myhost, sizeof(myhost))) { + case IF2IP_NOT_FOUND: + if(is_interface) { + /* Do not fall back to treating it as a host name */ + failf(data, "Couldn't bind to interface '%s'", dev); + return CURLE_INTERFACE_FAILED; + } + break; + case IF2IP_AF_NOT_SUPPORTED: + /* Signal the caller to try another address family if available */ + return CURLE_UNSUPPORTED_PROTOCOL; + case IF2IP_FOUND: + is_interface = TRUE; + /* + * We now have the numerical IP address in the 'myhost' buffer + */ + infof(data, "Local Interface %s is ip %s using address family %i", + dev, myhost, af); + done = 1; + break; + } + } + if(!is_interface) { + /* + * This was not an interface, resolve the name as a host name + * or IP number + * + * Temporarily force name resolution to use only the address type + * of the connection. The resolve functions should really be changed + * to take a type parameter instead. + */ + unsigned char ipver = conn->ip_version; + int rc; + + if(af == AF_INET) + conn->ip_version = CURL_IPRESOLVE_V4; +#ifdef ENABLE_IPV6 + else if(af == AF_INET6) + conn->ip_version = CURL_IPRESOLVE_V6; +#endif + + rc = Curl_resolv(data, dev, 80, FALSE, &h); + if(rc == CURLRESOLV_PENDING) + (void)Curl_resolver_wait_resolv(data, &h); + conn->ip_version = ipver; + + if(h) { + /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ + Curl_printable_address(h->addr, myhost, sizeof(myhost)); + infof(data, "Name '%s' family %i resolved to '%s' family %i", + dev, af, myhost, h->addr->ai_family); + Curl_resolv_unlock(data, h); + if(af != h->addr->ai_family) { + /* bad IP version combo, signal the caller to try another address + family if available */ + return CURLE_UNSUPPORTED_PROTOCOL; + } + done = 1; + } + else { + /* + * provided dev was no interface (or interfaces are not supported + * e.g. solaris) no ip address and no domain we fail here + */ + done = -1; + } + } + + if(done > 0) { +#ifdef ENABLE_IPV6 + /* IPv6 address */ + if(af == AF_INET6) { +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + char *scope_ptr = strchr(myhost, '%'); + if(scope_ptr) + *(scope_ptr++) = '\0'; +#endif + if(Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) { + si6->sin6_family = AF_INET6; + si6->sin6_port = htons(port); +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + if(scope_ptr) { + /* The "myhost" string either comes from Curl_if2ip or from + Curl_printable_address. The latter returns only numeric scope + IDs and the former returns none at all. So the scope ID, if + present, is known to be numeric */ + unsigned long scope_id = strtoul(scope_ptr, NULL, 10); + if(scope_id > UINT_MAX) + return CURLE_UNSUPPORTED_PROTOCOL; + + si6->sin6_scope_id = (unsigned int)scope_id; + } +#endif + } + sizeof_sa = sizeof(struct sockaddr_in6); + } + else +#endif + /* IPv4 address */ + if((af == AF_INET) && + (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) { + si4->sin_family = AF_INET; + si4->sin_port = htons(port); + sizeof_sa = sizeof(struct sockaddr_in); + } + } + + if(done < 1) { + /* errorbuf is set false so failf will overwrite any message already in + the error buffer, so the user receives this error message instead of a + generic resolve error. */ + data->state.errorbuf = FALSE; + failf(data, "Couldn't bind to '%s'", dev); + return CURLE_INTERFACE_FAILED; + } + } + else { + /* no device was given, prepare sa to match af's needs */ +#ifdef ENABLE_IPV6 + if(af == AF_INET6) { + si6->sin6_family = AF_INET6; + si6->sin6_port = htons(port); + sizeof_sa = sizeof(struct sockaddr_in6); + } + else +#endif + if(af == AF_INET) { + si4->sin_family = AF_INET; + si4->sin_port = htons(port); + sizeof_sa = sizeof(struct sockaddr_in); + } + } +#ifdef IP_BIND_ADDRESS_NO_PORT + (void)setsockopt(sockfd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &on, sizeof(on)); +#endif + for(;;) { + if(bind(sockfd, sock, sizeof_sa) >= 0) { + /* we succeeded to bind */ + struct Curl_sockaddr_storage add; + curl_socklen_t size = sizeof(add); + memset(&add, 0, sizeof(struct Curl_sockaddr_storage)); + if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) { + char buffer[STRERROR_LEN]; + data->state.os_errno = error = SOCKERRNO; + failf(data, "getsockname() failed with errno %d: %s", + error, Curl_strerror(error, buffer, sizeof(buffer))); + return CURLE_INTERFACE_FAILED; + } + infof(data, "Local port: %hu", port); + conn->bits.bound = TRUE; + return CURLE_OK; + } + + if(--portnum > 0) { + port++; /* try next port */ + if(port == 0) + break; + infof(data, "Bind to local port %d failed, trying next", port - 1); + /* We reuse/clobber the port variable here below */ + if(sock->sa_family == AF_INET) + si4->sin_port = ntohs(port); +#ifdef ENABLE_IPV6 + else + si6->sin6_port = ntohs(port); +#endif + } + else + break; + } + { + char buffer[STRERROR_LEN]; + data->state.os_errno = error = SOCKERRNO; + failf(data, "bind failed with errno %d: %s", + error, Curl_strerror(error, buffer, sizeof(buffer))); + } + + return CURLE_INTERFACE_FAILED; +} +#endif + +/* + * verifyconnect() returns TRUE if the connect really has happened. + */ +static bool verifyconnect(curl_socket_t sockfd, int *error) +{ + bool rc = TRUE; +#ifdef SO_ERROR + int err = 0; + curl_socklen_t errSize = sizeof(err); + +#ifdef _WIN32 + /* + * In October 2003 we effectively nullified this function on Windows due to + * problems with it using all CPU in multi-threaded cases. + * + * In May 2004, we bring it back to offer more info back on connect failures. + * Gisle Vanem could reproduce the former problems with this function, but + * could avoid them by adding this SleepEx() call below: + * + * "I don't have Rational Quantify, but the hint from his post was + * ntdll::NtRemoveIoCompletion(). So I'd assume the SleepEx (or maybe + * just Sleep(0) would be enough?) would release whatever + * mutex/critical-section the ntdll call is waiting on. + * + * Someone got to verify this on Win-NT 4.0, 2000." + */ + +#ifdef _WIN32_WCE + Sleep(0); +#else + SleepEx(0, FALSE); +#endif + +#endif + + if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize)) + err = SOCKERRNO; +#ifdef _WIN32_WCE + /* Old WinCE versions don't support SO_ERROR */ + if(WSAENOPROTOOPT == err) { + SET_SOCKERRNO(0); + err = 0; + } +#endif +#if defined(EBADIOCTL) && defined(__minix) + /* Minix 3.1.x doesn't support getsockopt on UDP sockets */ + if(EBADIOCTL == err) { + SET_SOCKERRNO(0); + err = 0; + } +#endif + if((0 == err) || (EISCONN == err)) + /* we are connected, awesome! */ + rc = TRUE; + else + /* This wasn't a successful connect */ + rc = FALSE; + if(error) + *error = err; +#else + (void)sockfd; + if(error) + *error = SOCKERRNO; +#endif + return rc; +} + +/** + * Determine the curl code for a socket connect() == -1 with errno. + */ +static CURLcode socket_connect_result(struct Curl_easy *data, + const char *ipaddress, int error) +{ + switch(error) { + case EINPROGRESS: + case EWOULDBLOCK: +#if defined(EAGAIN) +#if (EAGAIN) != (EWOULDBLOCK) + /* On some platforms EAGAIN and EWOULDBLOCK are the + * same value, and on others they are different, hence + * the odd #if + */ + case EAGAIN: +#endif +#endif + return CURLE_OK; + + default: + /* unknown error, fallthrough and try another address! */ +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)ipaddress; +#else + { + char buffer[STRERROR_LEN]; + infof(data, "Immediate connect fail for %s: %s", + ipaddress, Curl_strerror(error, buffer, sizeof(buffer))); + } +#endif + data->state.os_errno = error; + /* connect failed */ + return CURLE_COULDNT_CONNECT; + } +} + +/* We have a recv buffer to enhance reads with len < NW_SMALL_READS. + * This happens often on TLS connections where the TLS implementation + * tries to read the head of a TLS record, determine the length of the + * full record and then make a subsequent read for that. + * On large reads, we will not fill the buffer to avoid the double copy. */ +#define NW_RECV_CHUNK_SIZE (64 * 1024) +#define NW_RECV_CHUNKS 1 +#define NW_SMALL_READS (1024) + +struct cf_socket_ctx { + int transport; + struct Curl_sockaddr_ex addr; /* address to connect to */ + curl_socket_t sock; /* current attempt socket */ + struct bufq recvbuf; /* used when `buffer_recv` is set */ + char r_ip[MAX_IPADR_LEN]; /* remote IP as string */ + int r_port; /* remote port number */ + char l_ip[MAX_IPADR_LEN]; /* local IP as string */ + int l_port; /* local port number */ + struct curltime started_at; /* when socket was created */ + struct curltime connected_at; /* when socket connected/got first byte */ + struct curltime first_byte_at; /* when first byte was recvd */ + int error; /* errno of last failure or 0 */ +#ifdef DEBUGBUILD + int wblock_percent; /* percent of writes doing EAGAIN */ + int wpartial_percent; /* percent of bytes written in send */ + int rblock_percent; /* percent of reads doing EAGAIN */ + size_t recv_max; /* max enforced read size */ +#endif + BIT(got_first_byte); /* if first byte was received */ + BIT(accepted); /* socket was accepted, not connected */ + BIT(active); + BIT(buffer_recv); +}; + +static void cf_socket_ctx_init(struct cf_socket_ctx *ctx, + const struct Curl_addrinfo *ai, + int transport) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->sock = CURL_SOCKET_BAD; + ctx->transport = transport; + Curl_sock_assign_addr(&ctx->addr, ai, transport); + Curl_bufq_init(&ctx->recvbuf, NW_RECV_CHUNK_SIZE, NW_RECV_CHUNKS); +#ifdef DEBUGBUILD + { + char *p = getenv("CURL_DBG_SOCK_WBLOCK"); + if(p) { + long l = strtol(p, NULL, 10); + if(l >= 0 && l <= 100) + ctx->wblock_percent = (int)l; + } + p = getenv("CURL_DBG_SOCK_WPARTIAL"); + if(p) { + long l = strtol(p, NULL, 10); + if(l >= 0 && l <= 100) + ctx->wpartial_percent = (int)l; + } + p = getenv("CURL_DBG_SOCK_RBLOCK"); + if(p) { + long l = strtol(p, NULL, 10); + if(l >= 0 && l <= 100) + ctx->rblock_percent = (int)l; + } + p = getenv("CURL_DBG_SOCK_RMAX"); + if(p) { + long l = strtol(p, NULL, 10); + if(l >= 0) + ctx->recv_max = (size_t)l; + } + } +#endif +} + +struct reader_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; +}; + +static ssize_t nw_in_read(void *reader_ctx, + unsigned char *buf, size_t len, + CURLcode *err) +{ + struct reader_ctx *rctx = reader_ctx; + struct cf_socket_ctx *ctx = rctx->cf->ctx; + ssize_t nread; + + *err = CURLE_OK; + nread = sread(ctx->sock, buf, len); + + if(-1 == nread) { + int sockerr = SOCKERRNO; + + if( +#ifdef WSAEWOULDBLOCK + /* This is how Windows does it */ + (WSAEWOULDBLOCK == sockerr) +#else + /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned + due to its inability to send off data without blocking. We therefore + treat both error codes the same here */ + (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr) +#endif + ) { + /* this is just a case of EWOULDBLOCK */ + *err = CURLE_AGAIN; + nread = -1; + } + else { + char buffer[STRERROR_LEN]; + + failf(rctx->data, "Recv failure: %s", + Curl_strerror(sockerr, buffer, sizeof(buffer))); + rctx->data->state.os_errno = sockerr; + *err = CURLE_RECV_ERROR; + nread = -1; + } + } + CURL_TRC_CF(rctx->data, rctx->cf, "nw_in_read(len=%zu) -> %d, err=%d", + len, (int)nread, *err); + return nread; +} + +static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + if(ctx && CURL_SOCKET_BAD != ctx->sock) { + CURL_TRC_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T + ")", ctx->sock); + if(ctx->sock == cf->conn->sock[cf->sockindex]) + cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; + socket_close(data, cf->conn, !ctx->accepted, ctx->sock); + ctx->sock = CURL_SOCKET_BAD; + if(ctx->active && cf->sockindex == FIRSTSOCKET) + cf->conn->remote_addr = NULL; + Curl_bufq_reset(&ctx->recvbuf); + ctx->active = FALSE; + ctx->buffer_recv = FALSE; + memset(&ctx->started_at, 0, sizeof(ctx->started_at)); + memset(&ctx->connected_at, 0, sizeof(ctx->connected_at)); + } + + cf->connected = FALSE; +} + +static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + cf_socket_close(cf, data); + CURL_TRC_CF(data, cf, "destroy"); + Curl_bufq_free(&ctx->recvbuf); + free(ctx); + cf->ctx = NULL; +} + +static CURLcode set_local_ip(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + +#ifdef HAVE_GETSOCKNAME + if(!(data->conn->handler->protocol & CURLPROTO_TFTP)) { + /* TFTP does not connect, so it cannot get the IP like this */ + + char buffer[STRERROR_LEN]; + struct Curl_sockaddr_storage ssloc; + curl_socklen_t slen = sizeof(struct Curl_sockaddr_storage); + + memset(&ssloc, 0, sizeof(ssloc)); + if(getsockname(ctx->sock, (struct sockaddr*) &ssloc, &slen)) { + int error = SOCKERRNO; + failf(data, "getsockname() failed with errno %d: %s", + error, Curl_strerror(error, buffer, sizeof(buffer))); + return CURLE_FAILED_INIT; + } + if(!Curl_addr2string((struct sockaddr*)&ssloc, slen, + ctx->l_ip, &ctx->l_port)) { + failf(data, "ssloc inet_ntop() failed with errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + return CURLE_FAILED_INIT; + } + } +#else + (void)data; + ctx->l_ip[0] = 0; + ctx->l_port = -1; +#endif + return CURLE_OK; +} + +static CURLcode set_remote_ip(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + /* store remote address and port used in this connection attempt */ + if(!Curl_addr2string(&ctx->addr.sa_addr, ctx->addr.addrlen, + ctx->r_ip, &ctx->r_port)) { + char buffer[STRERROR_LEN]; + + ctx->error = errno; + /* malformed address or bug in inet_ntop, try next address */ + failf(data, "sa_addr inet_ntop() failed with errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + return CURLE_FAILED_INIT; + } + return CURLE_OK; +} + +static CURLcode cf_socket_open(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + int error = 0; + bool isconnected = FALSE; + CURLcode result = CURLE_COULDNT_CONNECT; + bool is_tcp; + + (void)data; + DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD); + ctx->started_at = Curl_now(); + result = socket_open(data, &ctx->addr, &ctx->sock); + if(result) + goto out; + + result = set_remote_ip(cf, data); + if(result) + goto out; + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + { + const char *ipmsg; +#ifdef ENABLE_IPV6 + if(ctx->addr.family == AF_INET6) { + set_ipv6_v6only(ctx->sock, 0); + ipmsg = " Trying [%s]:%d..."; + } + else +#endif + ipmsg = " Trying %s:%d..."; + infof(data, ipmsg, ctx->r_ip, ctx->r_port); + } +#endif + +#ifdef ENABLE_IPV6 + is_tcp = (ctx->addr.family == AF_INET + || ctx->addr.family == AF_INET6) && + ctx->addr.socktype == SOCK_STREAM; +#else + is_tcp = (ctx->addr.family == AF_INET) && + ctx->addr.socktype == SOCK_STREAM; +#endif + if(is_tcp && data->set.tcp_nodelay) + tcpnodelay(data, ctx->sock); + + nosigpipe(data, ctx->sock); + + Curl_sndbufset(ctx->sock); + + if(is_tcp && data->set.tcp_keepalive) + tcpkeepalive(data, ctx->sock); + + if(data->set.fsockopt) { + /* activate callback for setting socket options */ + Curl_set_in_callback(data, true); + error = data->set.fsockopt(data->set.sockopt_client, + ctx->sock, + CURLSOCKTYPE_IPCXN); + Curl_set_in_callback(data, false); + + if(error == CURL_SOCKOPT_ALREADY_CONNECTED) + isconnected = TRUE; + else if(error) { + result = CURLE_ABORTED_BY_CALLBACK; + goto out; + } + } + +#ifndef CURL_DISABLE_BINDLOCAL + /* possibly bind the local end to an IP, interface or port */ + if(ctx->addr.family == AF_INET +#ifdef ENABLE_IPV6 + || ctx->addr.family == AF_INET6 +#endif + ) { + result = bindlocal(data, cf->conn, ctx->sock, ctx->addr.family, + Curl_ipv6_scope(&ctx->addr.sa_addr)); + if(result) { + if(result == CURLE_UNSUPPORTED_PROTOCOL) { + /* The address family is not supported on this interface. + We can continue trying addresses */ + result = CURLE_COULDNT_CONNECT; + } + goto out; + } + } +#endif + + /* set socket non-blocking */ + (void)curlx_nonblock(ctx->sock, TRUE); + +out: + if(result) { + if(ctx->sock != CURL_SOCKET_BAD) { + socket_close(data, cf->conn, TRUE, ctx->sock); + ctx->sock = CURL_SOCKET_BAD; + } + } + else if(isconnected) { + set_local_ip(cf, data); + ctx->connected_at = Curl_now(); + cf->connected = TRUE; + } + CURL_TRC_CF(data, cf, "cf_socket_open() -> %d, fd=%" CURL_FORMAT_SOCKET_T, + result, ctx->sock); + return result; +} + +static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data, + bool is_tcp_fastopen) +{ + struct cf_socket_ctx *ctx = cf->ctx; +#ifdef TCP_FASTOPEN_CONNECT + int optval = 1; +#endif + int rc = -1; + + (void)data; + if(is_tcp_fastopen) { +#if defined(CONNECT_DATA_IDEMPOTENT) /* Darwin */ +# if defined(HAVE_BUILTIN_AVAILABLE) + /* while connectx function is available since macOS 10.11 / iOS 9, + it did not have the interface declared correctly until + Xcode 9 / macOS SDK 10.13 */ + if(__builtin_available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) { + sa_endpoints_t endpoints; + endpoints.sae_srcif = 0; + endpoints.sae_srcaddr = NULL; + endpoints.sae_srcaddrlen = 0; + endpoints.sae_dstaddr = &ctx->addr.sa_addr; + endpoints.sae_dstaddrlen = ctx->addr.addrlen; + + rc = connectx(ctx->sock, &endpoints, SAE_ASSOCID_ANY, + CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT, + NULL, 0, NULL, NULL); + } + else { + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + } +# else + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); +# endif /* HAVE_BUILTIN_AVAILABLE */ +#elif defined(TCP_FASTOPEN_CONNECT) /* Linux >= 4.11 */ + if(setsockopt(ctx->sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, + (void *)&optval, sizeof(optval)) < 0) + infof(data, "Failed to enable TCP Fast Open on fd %" + CURL_FORMAT_SOCKET_T, ctx->sock); + + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); +#elif defined(MSG_FASTOPEN) /* old Linux */ + if(cf->conn->given->flags & PROTOPT_SSL) + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + else + rc = 0; /* Do nothing */ +#endif + } + else { + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + } + return rc; +} + +static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_socket_ctx *ctx = cf->ctx; + CURLcode result = CURLE_COULDNT_CONNECT; + int rc = 0; + + (void)data; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* TODO: need to support blocking connect? */ + if(blocking) + return CURLE_UNSUPPORTED_PROTOCOL; + + *done = FALSE; /* a very negative world view is best */ + if(ctx->sock == CURL_SOCKET_BAD) { + int error; + + result = cf_socket_open(cf, data); + if(result) + goto out; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* Connect TCP socket */ + rc = do_connect(cf, data, cf->conn->bits.tcp_fastopen); + error = SOCKERRNO; + set_local_ip(cf, data); + CURL_TRC_CF(data, cf, "local address %s port %d...", + ctx->l_ip, ctx->l_port); + if(-1 == rc) { + result = socket_connect_result(data, ctx->r_ip, error); + goto out; + } + } + +#ifdef mpeix + /* Call this function once now, and ignore the results. We do this to + "clear" the error state on the socket so that we can later read it + reliably. This is reported necessary on the MPE/iX operating + system. */ + (void)verifyconnect(ctx->sock, NULL); +#endif + /* check socket for connect */ + rc = SOCKET_WRITABLE(ctx->sock, 0); + + if(rc == 0) { /* no connection yet */ + CURL_TRC_CF(data, cf, "not connected yet"); + return CURLE_OK; + } + else if(rc == CURL_CSELECT_OUT || cf->conn->bits.tcp_fastopen) { + if(verifyconnect(ctx->sock, &ctx->error)) { + /* we are connected with TCP, awesome! */ + ctx->connected_at = Curl_now(); + set_local_ip(cf, data); + *done = TRUE; + cf->connected = TRUE; + CURL_TRC_CF(data, cf, "connected"); + return CURLE_OK; + } + } + else if(rc & CURL_CSELECT_ERR) { + (void)verifyconnect(ctx->sock, &ctx->error); + result = CURLE_COULDNT_CONNECT; + } + +out: + if(result) { + if(ctx->error) { + set_local_ip(cf, data); + data->state.os_errno = ctx->error; + SET_SOCKERRNO(ctx->error); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + { + char buffer[STRERROR_LEN]; + infof(data, "connect to %s port %u from %s port %d failed: %s", + ctx->r_ip, ctx->r_port, ctx->l_ip, ctx->l_port, + Curl_strerror(ctx->error, buffer, sizeof(buffer))); + } +#endif + } + if(ctx->sock != CURL_SOCKET_BAD) { + socket_close(data, cf->conn, TRUE, ctx->sock); + ctx->sock = CURL_SOCKET_BAD; + } + *done = FALSE; + } + return result; +} + +static void cf_socket_get_host(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char **phost, + const char **pdisplay_host, + int *pport) +{ + (void)data; + *phost = cf->conn->host.name; + *pdisplay_host = cf->conn->host.dispname; + *pport = cf->conn->port; +} + +static void cf_socket_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + if(ctx->sock != CURL_SOCKET_BAD) { + if(!cf->connected) + Curl_pollset_set_out_only(data, ps, ctx->sock); + else + Curl_pollset_add_in(data, ps, ctx->sock); + CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num); + } +} + +static bool cf_socket_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + int readable; + + (void)data; + if(!Curl_bufq_is_empty(&ctx->recvbuf)) + return TRUE; + + readable = SOCKET_READABLE(ctx->sock, 0); + return (readable > 0 && (readable & CURL_CSELECT_IN)); +} + +static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_socket_ctx *ctx = cf->ctx; + curl_socket_t fdsave; + ssize_t nwritten; + size_t orig_len = len; + + *err = CURLE_OK; + fdsave = cf->conn->sock[cf->sockindex]; + cf->conn->sock[cf->sockindex] = ctx->sock; + +#ifdef DEBUGBUILD + /* simulate network blocking/partial writes */ + if(ctx->wblock_percent > 0) { + unsigned char c; + Curl_rand(data, &c, 1); + if(c >= ((100-ctx->wblock_percent)*256/100)) { + CURL_TRC_CF(data, cf, "send(len=%zu) SIMULATE EWOULDBLOCK", orig_len); + *err = CURLE_AGAIN; + nwritten = -1; + cf->conn->sock[cf->sockindex] = fdsave; + return nwritten; + } + } + if(cf->cft != &Curl_cft_udp && ctx->wpartial_percent > 0 && len > 8) { + len = len * ctx->wpartial_percent / 100; + if(!len) + len = 1; + CURL_TRC_CF(data, cf, "send(len=%zu) SIMULATE partial write of %zu bytes", + orig_len, len); + } +#endif + +#if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT) /* Linux */ + if(cf->conn->bits.tcp_fastopen) { + nwritten = sendto(ctx->sock, buf, len, MSG_FASTOPEN, + &cf->conn->remote_addr->sa_addr, + cf->conn->remote_addr->addrlen); + cf->conn->bits.tcp_fastopen = FALSE; + } + else +#endif + nwritten = swrite(ctx->sock, buf, len); + + if(-1 == nwritten) { + int sockerr = SOCKERRNO; + + if( +#ifdef WSAEWOULDBLOCK + /* This is how Windows does it */ + (WSAEWOULDBLOCK == sockerr) +#else + /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned + due to its inability to send off data without blocking. We therefore + treat both error codes the same here */ + (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr) || + (EINPROGRESS == sockerr) +#endif + ) { + /* this is just a case of EWOULDBLOCK */ + *err = CURLE_AGAIN; + } + else { + char buffer[STRERROR_LEN]; + failf(data, "Send failure: %s", + Curl_strerror(sockerr, buffer, sizeof(buffer))); + data->state.os_errno = sockerr; + *err = CURLE_SEND_ERROR; + } + } + + CURL_TRC_CF(data, cf, "send(len=%zu) -> %d, err=%d", + orig_len, (int)nwritten, *err); + cf->conn->sock[cf->sockindex] = fdsave; + return nwritten; +} + +static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_socket_ctx *ctx = cf->ctx; + curl_socket_t fdsave; + ssize_t nread; + + *err = CURLE_OK; + + fdsave = cf->conn->sock[cf->sockindex]; + cf->conn->sock[cf->sockindex] = ctx->sock; + +#ifdef DEBUGBUILD + /* simulate network blocking/partial reads */ + if(cf->cft != &Curl_cft_udp && ctx->rblock_percent > 0) { + unsigned char c; + Curl_rand(data, &c, 1); + if(c >= ((100-ctx->rblock_percent)*256/100)) { + CURL_TRC_CF(data, cf, "recv(len=%zu) SIMULATE EWOULDBLOCK", len); + *err = CURLE_AGAIN; + nread = -1; + cf->conn->sock[cf->sockindex] = fdsave; + return nread; + } + } + if(cf->cft != &Curl_cft_udp && ctx->recv_max && ctx->recv_max < len) { + size_t orig_len = len; + len = ctx->recv_max; + CURL_TRC_CF(data, cf, "recv(len=%zu) SIMULATE max read of %zu bytes", + orig_len, len); + } +#endif + + if(ctx->buffer_recv && !Curl_bufq_is_empty(&ctx->recvbuf)) { + CURL_TRC_CF(data, cf, "recv from buffer"); + nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err); + } + else { + struct reader_ctx rctx; + + rctx.cf = cf; + rctx.data = data; + + /* "small" reads may trigger filling our buffer, "large" reads + * are probably not worth the additional copy */ + if(ctx->buffer_recv && len < NW_SMALL_READS) { + ssize_t nwritten; + nwritten = Curl_bufq_slurp(&ctx->recvbuf, nw_in_read, &rctx, err); + if(nwritten < 0 && !Curl_bufq_is_empty(&ctx->recvbuf)) { + /* we have a partial read with an error. need to deliver + * what we got, return the error later. */ + CURL_TRC_CF(data, cf, "partial read: empty buffer first"); + nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err); + } + else if(nwritten < 0) { + nread = -1; + goto out; + } + else if(nwritten == 0) { + /* eof */ + *err = CURLE_OK; + nread = 0; + } + else { + CURL_TRC_CF(data, cf, "buffered %zd additional bytes", nwritten); + nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err); + } + } + else { + nread = nw_in_read(&rctx, (unsigned char *)buf, len, err); + } + } + +out: + CURL_TRC_CF(data, cf, "recv(len=%zu) -> %d, err=%d", len, (int)nread, + *err); + if(nread > 0 && !ctx->got_first_byte) { + ctx->first_byte_at = Curl_now(); + ctx->got_first_byte = TRUE; + } + cf->conn->sock[cf->sockindex] = fdsave; + return nread; +} + +static void conn_set_primary_ip(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ +#ifdef HAVE_GETPEERNAME + struct cf_socket_ctx *ctx = cf->ctx; + if(!(data->conn->handler->protocol & CURLPROTO_TFTP)) { + /* TFTP does not connect the endpoint: getpeername() failed with errno + 107: Transport endpoint is not connected */ + + char buffer[STRERROR_LEN]; + struct Curl_sockaddr_storage ssrem; + curl_socklen_t plen; + int port; + + plen = sizeof(ssrem); + memset(&ssrem, 0, plen); + if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) { + int error = SOCKERRNO; + failf(data, "getpeername() failed with errno %d: %s", + error, Curl_strerror(error, buffer, sizeof(buffer))); + return; + } + if(!Curl_addr2string((struct sockaddr*)&ssrem, plen, + cf->conn->primary_ip, &port)) { + failf(data, "ssrem inet_ntop() failed with errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + return; + } + } +#else + cf->conn->primary_ip[0] = 0; + (void)data; +#endif +} + +static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + /* use this socket from now on */ + cf->conn->sock[cf->sockindex] = ctx->sock; + /* the first socket info gets set at conn and data */ + if(cf->sockindex == FIRSTSOCKET) { + cf->conn->remote_addr = &ctx->addr; + #ifdef ENABLE_IPV6 + cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE; + #endif + conn_set_primary_ip(cf, data); + set_local_ip(cf, data); + Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); + /* buffering is currently disabled by default because we have stalls + * in parallel transfers where not all buffered data is consumed and no + * socket events happen. + */ + ctx->buffer_recv = FALSE; + } + ctx->active = TRUE; +} + +static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_CONN_INFO_UPDATE: + cf_socket_active(cf, data); + break; + case CF_CTRL_DATA_SETUP: + Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); + break; + case CF_CTRL_FORGET_SOCKET: + ctx->sock = CURL_SOCKET_BAD; + break; + } + return CURLE_OK; +} + +static bool cf_socket_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_socket_ctx *ctx = cf->ctx; + struct pollfd pfd[1]; + int r; + + *input_pending = FALSE; + (void)data; + if(!ctx || ctx->sock == CURL_SOCKET_BAD) + return FALSE; + + /* Check with 0 timeout if there are any events pending on the socket */ + pfd[0].fd = ctx->sock; + pfd[0].events = POLLRDNORM|POLLIN|POLLRDBAND|POLLPRI; + pfd[0].revents = 0; + + r = Curl_poll(pfd, 1, 0); + if(r < 0) { + CURL_TRC_CF(data, cf, "is_alive: poll error, assume dead"); + return FALSE; + } + else if(r == 0) { + CURL_TRC_CF(data, cf, "is_alive: poll timeout, assume alive"); + return TRUE; + } + else if(pfd[0].revents & (POLLERR|POLLHUP|POLLPRI|POLLNVAL)) { + CURL_TRC_CF(data, cf, "is_alive: err/hup/etc events, assume dead"); + return FALSE; + } + + CURL_TRC_CF(data, cf, "is_alive: valid events, looks alive"); + *input_pending = TRUE; + return TRUE; +} + +static CURLcode cf_socket_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_SOCKET: + DEBUGASSERT(pres2); + *((curl_socket_t *)pres2) = ctx->sock; + return CURLE_OK; + case CF_QUERY_CONNECT_REPLY_MS: + if(ctx->got_first_byte) { + timediff_t ms = Curl_timediff(ctx->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; + 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; + } + default: + break; + } + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +struct Curl_cftype Curl_cft_tcp = { + "TCP", + CF_TYPE_IP_CONNECT, + CURL_LOG_LVL_NONE, + cf_socket_destroy, + cf_tcp_connect, + cf_socket_close, + cf_socket_get_host, + cf_socket_adjust_pollset, + cf_socket_data_pending, + cf_socket_send, + cf_socket_recv, + cf_socket_cntrl, + cf_socket_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_socket_query, +}; + +CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport) +{ + struct cf_socket_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + (void)data; + (void)conn; + DEBUGASSERT(transport == TRNSPRT_TCP); + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + cf_socket_ctx_init(ctx, ai, transport); + + result = Curl_cf_create(&cf, &Curl_cft_tcp, ctx); + +out: + *pcf = (!result)? cf : NULL; + if(result) { + Curl_safefree(cf); + Curl_safefree(ctx); + } + + return result; +} + +static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + int rc; + + /* QUIC needs a connected socket, nonblocking */ + DEBUGASSERT(ctx->sock != CURL_SOCKET_BAD); + + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + if(-1 == rc) { + return socket_connect_result(data, ctx->r_ip, SOCKERRNO); + } + set_local_ip(cf, data); + CURL_TRC_CF(data, cf, "%s socket %" CURL_FORMAT_SOCKET_T + " connected: [%s:%d] -> [%s:%d]", + (ctx->transport == TRNSPRT_QUIC)? "QUIC" : "UDP", + ctx->sock, ctx->l_ip, ctx->l_port, ctx->r_ip, ctx->r_port); + + (void)curlx_nonblock(ctx->sock, TRUE); + switch(ctx->addr.family) { +#if defined(__linux__) && defined(IP_MTU_DISCOVER) + case AF_INET: { + int val = IP_PMTUDISC_DO; + (void)setsockopt(ctx->sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, + sizeof(val)); + break; + } +#endif +#if defined(__linux__) && defined(IPV6_MTU_DISCOVER) + case AF_INET6: { + int val = IPV6_PMTUDISC_DO; + (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val, + sizeof(val)); + break; + } +#endif + } + return CURLE_OK; +} + +static CURLcode cf_udp_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_socket_ctx *ctx = cf->ctx; + CURLcode result = CURLE_COULDNT_CONNECT; + + (void)blocking; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + *done = FALSE; + if(ctx->sock == CURL_SOCKET_BAD) { + result = cf_socket_open(cf, data); + if(result) { + CURL_TRC_CF(data, cf, "cf_udp_connect(), open failed -> %d", result); + goto out; + } + + if(ctx->transport == TRNSPRT_QUIC) { + result = cf_udp_setup_quic(cf, data); + if(result) + goto out; + CURL_TRC_CF(data, cf, "cf_udp_connect(), opened socket=%" + CURL_FORMAT_SOCKET_T " (%s:%d)", + ctx->sock, ctx->l_ip, ctx->l_port); + } + else { + CURL_TRC_CF(data, cf, "cf_udp_connect(), opened socket=%" + CURL_FORMAT_SOCKET_T " (unconnected)", ctx->sock); + } + *done = TRUE; + cf->connected = TRUE; + } +out: + return result; +} + +struct Curl_cftype Curl_cft_udp = { + "UDP", + CF_TYPE_IP_CONNECT, + CURL_LOG_LVL_NONE, + cf_socket_destroy, + cf_udp_connect, + cf_socket_close, + cf_socket_get_host, + cf_socket_adjust_pollset, + cf_socket_data_pending, + cf_socket_send, + cf_socket_recv, + cf_socket_cntrl, + cf_socket_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_socket_query, +}; + +CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport) +{ + struct cf_socket_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + (void)data; + (void)conn; + DEBUGASSERT(transport == TRNSPRT_UDP || transport == TRNSPRT_QUIC); + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + cf_socket_ctx_init(ctx, ai, transport); + + result = Curl_cf_create(&cf, &Curl_cft_udp, ctx); + +out: + *pcf = (!result)? cf : NULL; + if(result) { + Curl_safefree(cf); + Curl_safefree(ctx); + } + + return result; +} + +/* this is the TCP filter which can also handle this case */ +struct Curl_cftype Curl_cft_unix = { + "UNIX", + CF_TYPE_IP_CONNECT, + CURL_LOG_LVL_NONE, + cf_socket_destroy, + cf_tcp_connect, + cf_socket_close, + cf_socket_get_host, + cf_socket_adjust_pollset, + cf_socket_data_pending, + cf_socket_send, + cf_socket_recv, + cf_socket_cntrl, + cf_socket_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_socket_query, +}; + +CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport) +{ + struct cf_socket_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + (void)data; + (void)conn; + DEBUGASSERT(transport == TRNSPRT_UNIX); + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + cf_socket_ctx_init(ctx, ai, transport); + + result = Curl_cf_create(&cf, &Curl_cft_unix, ctx); + +out: + *pcf = (!result)? cf : NULL; + if(result) { + Curl_safefree(cf); + Curl_safefree(ctx); + } + + return result; +} + +static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + /* we start accepted, if we ever close, we cannot go on */ + (void)data; + (void)blocking; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + return CURLE_FAILED_INIT; +} + +struct Curl_cftype Curl_cft_tcp_accept = { + "TCP-ACCEPT", + CF_TYPE_IP_CONNECT, + CURL_LOG_LVL_NONE, + cf_socket_destroy, + cf_tcp_accept_connect, + cf_socket_close, + cf_socket_get_host, /* TODO: not accurate */ + cf_socket_adjust_pollset, + cf_socket_data_pending, + cf_socket_send, + cf_socket_recv, + cf_socket_cntrl, + cf_socket_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_socket_query, +}; + +CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, curl_socket_t *s) +{ + CURLcode result; + struct Curl_cfilter *cf = NULL; + struct cf_socket_ctx *ctx = NULL; + + /* replace any existing */ + Curl_conn_cf_discard_all(data, conn, sockindex); + DEBUGASSERT(conn->sock[sockindex] == CURL_SOCKET_BAD); + + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->transport = conn->transport; + ctx->sock = *s; + ctx->accepted = FALSE; + result = Curl_cf_create(&cf, &Curl_cft_tcp_accept, ctx); + if(result) + goto out; + Curl_conn_cf_add(data, conn, sockindex, cf); + + conn->sock[sockindex] = ctx->sock; + set_local_ip(cf, data); + ctx->active = TRUE; + ctx->connected_at = Curl_now(); + cf->connected = TRUE; + CURL_TRC_CF(data, cf, "Curl_conn_tcp_listen_set(%" + CURL_FORMAT_SOCKET_T ")", ctx->sock); + +out: + if(result) { + Curl_safefree(cf); + Curl_safefree(ctx); + } + return result; +} + +static void set_accepted_remote_ip(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; +#ifdef HAVE_GETPEERNAME + char buffer[STRERROR_LEN]; + struct Curl_sockaddr_storage ssrem; + curl_socklen_t plen; + + ctx->r_ip[0] = 0; + ctx->r_port = 0; + plen = sizeof(ssrem); + memset(&ssrem, 0, plen); + if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) { + int error = SOCKERRNO; + failf(data, "getpeername() failed with errno %d: %s", + error, Curl_strerror(error, buffer, sizeof(buffer))); + return; + } + if(!Curl_addr2string((struct sockaddr*)&ssrem, plen, + ctx->r_ip, &ctx->r_port)) { + failf(data, "ssrem inet_ntop() failed with errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + return; + } +#else + ctx->r_ip[0] = 0; + ctx->r_port = 0; + (void)data; +#endif +} + +CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, curl_socket_t *s) +{ + struct Curl_cfilter *cf = NULL; + struct cf_socket_ctx *ctx = NULL; + + cf = conn->cfilter[sockindex]; + if(!cf || cf->cft != &Curl_cft_tcp_accept) + return CURLE_FAILED_INIT; + + ctx = cf->ctx; + /* discard the listen socket */ + socket_close(data, conn, TRUE, ctx->sock); + ctx->sock = *s; + conn->sock[sockindex] = ctx->sock; + set_accepted_remote_ip(cf, data); + set_local_ip(cf, data); + ctx->active = TRUE; + ctx->accepted = TRUE; + ctx->connected_at = Curl_now(); + cf->connected = TRUE; + CURL_TRC_CF(data, cf, "accepted_set(sock=%" CURL_FORMAT_SOCKET_T + ", remote=%s port=%d)", + ctx->sock, ctx->r_ip, ctx->r_port); + + return CURLE_OK; +} + +/** + * Return TRUE iff `cf` is a socket filter. + */ +static bool cf_is_socket(struct Curl_cfilter *cf) +{ + return cf && (cf->cft == &Curl_cft_tcp || + cf->cft == &Curl_cft_udp || + cf->cft == &Curl_cft_unix || + cf->cft == &Curl_cft_tcp_accept); +} + +CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *psock, + const struct Curl_sockaddr_ex **paddr, + const char **pr_ip_str, int *pr_port, + const char **pl_ip_str, int *pl_port) +{ + if(cf_is_socket(cf) && cf->ctx) { + struct cf_socket_ctx *ctx = cf->ctx; + + if(psock) + *psock = ctx->sock; + if(paddr) + *paddr = &ctx->addr; + if(pr_ip_str) + *pr_ip_str = ctx->r_ip; + if(pr_port) + *pr_port = ctx->r_port; + if(pl_port ||pl_ip_str) { + set_local_ip(cf, data); + if(pl_ip_str) + *pl_ip_str = ctx->l_ip; + if(pl_port) + *pl_port = ctx->l_port; + } + return CURLE_OK; + } + return CURLE_FAILED_INIT; +} diff --git a/Utilities/cmcurl/lib/cf-socket.h b/Utilities/cmcurl/lib/cf-socket.h new file mode 100644 index 0000000..1d40df7 --- /dev/null +++ b/Utilities/cmcurl/lib/cf-socket.h @@ -0,0 +1,191 @@ +#ifndef HEADER_CURL_CF_SOCKET_H +#define HEADER_CURL_CF_SOCKET_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#include "nonblock.h" /* for curlx_nonblock(), formerly Curl_nonblock() */ +#include "sockaddr.h" + +struct Curl_addrinfo; +struct Curl_cfilter; +struct Curl_easy; +struct connectdata; +struct Curl_sockaddr_ex; + +#ifndef SIZEOF_CURL_SOCKET_T +/* configure and cmake check and set the define */ +# ifdef _WIN64 +# define SIZEOF_CURL_SOCKET_T 8 +# else +/* default guess */ +# define SIZEOF_CURL_SOCKET_T 4 +# endif +#endif + +#if SIZEOF_CURL_SOCKET_T < 8 +# define CURL_FORMAT_SOCKET_T "d" +#else +# define CURL_FORMAT_SOCKET_T "qd" +#endif + + +/* + * The Curl_sockaddr_ex structure is basically libcurl's external API + * curl_sockaddr structure with enough space available to directly hold any + * protocol-specific address structures. The variable declared here will be + * used to pass / receive data to/from the fopensocket callback if this has + * been set, before that, it is initialized from parameters. + */ +struct Curl_sockaddr_ex { + int family; + int socktype; + int protocol; + unsigned int addrlen; + union { + struct sockaddr addr; + struct Curl_sockaddr_storage buff; + } _sa_ex_u; +}; +#define sa_addr _sa_ex_u.addr + + +/* + * Create a socket based on info from 'conn' and 'ai'. + * + * Fill in 'addr' and 'sockfd' accordingly if OK is returned. If the open + * socket callback is set, used that! + * + */ +CURLcode Curl_socket_open(struct Curl_easy *data, + const struct Curl_addrinfo *ai, + struct Curl_sockaddr_ex *addr, + int transport, + curl_socket_t *sockfd); + +int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t sock); + +#ifdef USE_WINSOCK +/* When you run a program that uses the Windows Sockets API, you may + experience slow performance when you copy data to a TCP server. + + https://support.microsoft.com/kb/823764 + + Work-around: Make the Socket Send Buffer Size Larger Than the Program Send + Buffer Size + +*/ +void Curl_sndbufset(curl_socket_t sockfd); +#else +#define Curl_sndbufset(y) Curl_nop_stmt +#endif + +/** + * Assign the address `ai` to the Curl_sockaddr_ex `dest` and + * set the transport used. + */ +void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, + const struct Curl_addrinfo *ai, + int transport); + +/** + * Creates a cfilter that opens a TCP socket to the given address + * when calling its `connect` implementation. + * The filter will not touch any connection/data flags and can be + * used in happy eyeballing. Once selected for use, its `_active()` + * method needs to be called. + */ +CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport); + +/** + * Creates a cfilter that opens a UDP socket to the given address + * when calling its `connect` implementation. + * The filter will not touch any connection/data flags and can be + * used in happy eyeballing. Once selected for use, its `_active()` + * method needs to be called. + */ +CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport); + +/** + * Creates a cfilter that opens a UNIX socket to the given address + * when calling its `connect` implementation. + * The filter will not touch any connection/data flags and can be + * used in happy eyeballing. Once selected for use, its `_active()` + * method needs to be called. + */ +CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport); + +/** + * Creates a cfilter that keeps a listening socket. + */ +CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + curl_socket_t *s); + +/** + * Replace the listen socket with the accept()ed one. + */ +CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + curl_socket_t *s); + +/** + * Peek at the socket and remote ip/port the socket filter is using. + * The filter owns all returned values. + * @param psock pointer to hold socket descriptor or NULL + * @param paddr pointer to hold addr reference or NULL + * @param pr_ip_str pointer to hold remote addr as string or NULL + * @param pr_port pointer to hold remote port number or NULL + * @param pl_ip_str pointer to hold local addr as string or NULL + * @param pl_port pointer to hold local port number or NULL + * Returns error if the filter is of invalid type. + */ +CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *psock, + const struct Curl_sockaddr_ex **paddr, + const char **pr_ip_str, int *pr_port, + const char **pl_ip_str, int *pl_port); + +extern struct Curl_cftype Curl_cft_tcp; +extern struct Curl_cftype Curl_cft_udp; +extern struct Curl_cftype Curl_cft_unix; +extern struct Curl_cftype Curl_cft_tcp_accept; + +#endif /* HEADER_CURL_CF_SOCKET_H */ diff --git a/Utilities/cmcurl/lib/cfilters.c b/Utilities/cmcurl/lib/cfilters.c new file mode 100644 index 0000000..e78ecd7 --- /dev/null +++ b/Utilities/cmcurl/lib/cfilters.c @@ -0,0 +1,802 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include "strerror.h" +#include "cfilters.h" +#include "connect.h" +#include "url.h" /* for Curl_safefree() */ +#include "sendf.h" +#include "sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "multiif.h" +#include "progress.h" +#include "select.h" +#include "warnless.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +#ifdef DEBUGBUILD +/* used by unit2600.c */ +void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + cf->connected = FALSE; + if(cf->next) + cf->next->cft->do_close(cf->next, data); +} +#endif + +static void conn_report_connect_stats(struct Curl_easy *data, + struct connectdata *conn); + +void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data, + const char **phost, const char **pdisplay_host, + int *pport) +{ + if(cf->next) + cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport); + else { + *phost = cf->conn->host.name; + *pdisplay_host = cf->conn->host.dispname; + *pport = cf->conn->port; + } +} + +void Curl_cf_def_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + /* NOP */ + (void)cf; + (void)data; + (void)ps; +} + +bool Curl_cf_def_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + return cf->next? + cf->next->cft->has_data_pending(cf->next, data) : FALSE; +} + +ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + return cf->next? + cf->next->cft->do_send(cf->next, data, buf, len, err) : + CURLE_RECV_ERROR; +} + +ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + return cf->next? + cf->next->cft->do_recv(cf->next, data, buf, len, err) : + CURLE_SEND_ERROR; +} + +bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + return cf->next? + cf->next->cft->is_alive(cf->next, data, input_pending) : + FALSE; /* pessimistic in absence of data */ +} + +CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + return cf->next? + cf->next->cft->keep_alive(cf->next, data) : + CURLE_OK; +} + +CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, + struct Curl_easy *data) +{ + struct Curl_cfilter *cfn, *cf = *pcf; + + if(cf) { + *pcf = NULL; + while(cf) { + cfn = cf->next; + /* prevent destroying filter to mess with its sub-chain, since + * we have the reference now and will call destroy on it. + */ + cf->next = NULL; + cf->cft->destroy(cf, data); + free(cf); + cf = cfn; + } + } +} + +void Curl_conn_cf_discard_all(struct Curl_easy *data, + struct connectdata *conn, int index) +{ + Curl_conn_cf_discard_chain(&conn->cfilter[index], data); +} + +void Curl_conn_close(struct Curl_easy *data, int index) +{ + struct Curl_cfilter *cf; + + DEBUGASSERT(data->conn); + /* it is valid to call that without filters being present */ + cf = data->conn->cfilter[index]; + if(cf) { + cf->cft->do_close(cf, data); + } +} + +ssize_t Curl_conn_recv(struct Curl_easy *data, int num, char *buf, + size_t len, CURLcode *code) +{ + struct Curl_cfilter *cf; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + cf = data->conn->cfilter[num]; + while(cf && !cf->connected) { + cf = cf->next; + } + if(cf) { + return cf->cft->do_recv(cf, data, buf, len, code); + } + failf(data, "recv: no filter connected"); + *code = CURLE_FAILED_INIT; + return -1; +} + +ssize_t Curl_conn_send(struct Curl_easy *data, int num, + const void *mem, size_t len, CURLcode *code) +{ + struct Curl_cfilter *cf; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + cf = data->conn->cfilter[num]; + while(cf && !cf->connected) { + cf = cf->next; + } + if(cf) { + return cf->cft->do_send(cf, data, mem, len, code); + } + failf(data, "send: no filter connected"); + DEBUGASSERT(0); + *code = CURLE_FAILED_INIT; + return -1; +} + +CURLcode Curl_cf_create(struct Curl_cfilter **pcf, + const struct Curl_cftype *cft, + void *ctx) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OUT_OF_MEMORY; + + DEBUGASSERT(cft); + cf = calloc(1, sizeof(*cf)); + if(!cf) + goto out; + + cf->cft = cft; + cf->ctx = ctx; + result = CURLE_OK; +out: + *pcf = cf; + return result; +} + +void Curl_conn_cf_add(struct Curl_easy *data, + struct connectdata *conn, + int index, + struct Curl_cfilter *cf) +{ + (void)data; + DEBUGASSERT(conn); + DEBUGASSERT(!cf->conn); + DEBUGASSERT(!cf->next); + + cf->next = conn->cfilter[index]; + cf->conn = conn; + cf->sockindex = index; + conn->cfilter[index] = cf; + CURL_TRC_CF(data, cf, "added"); +} + +void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at, + struct Curl_cfilter *cf_new) +{ + struct Curl_cfilter *tail, **pnext; + + DEBUGASSERT(cf_at); + DEBUGASSERT(cf_new); + DEBUGASSERT(!cf_new->conn); + + tail = cf_at->next; + cf_at->next = cf_new; + do { + cf_new->conn = cf_at->conn; + cf_new->sockindex = cf_at->sockindex; + pnext = &cf_new->next; + cf_new = cf_new->next; + } while(cf_new); + *pnext = tail; +} + +bool Curl_conn_cf_discard_sub(struct Curl_cfilter *cf, + struct Curl_cfilter *discard, + struct Curl_easy *data, + bool destroy_always) +{ + struct Curl_cfilter **pprev = &cf->next; + bool found = FALSE; + + /* remove from sub-chain and destroy */ + DEBUGASSERT(cf); + while(*pprev) { + if(*pprev == cf) { + *pprev = discard->next; + discard->next = NULL; + found = TRUE; + break; + } + pprev = &((*pprev)->next); + } + if(found || destroy_always) { + discard->next = NULL; + discard->cft->destroy(discard, data); + free(discard); + } + return found; +} + +CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + if(cf) + return cf->cft->do_connect(cf, data, blocking, done); + return CURLE_FAILED_INIT; +} + +void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + if(cf) + cf->cft->do_close(cf, data); +} + +ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + if(cf) + return cf->cft->do_send(cf, data, buf, len, err); + *err = CURLE_SEND_ERROR; + return -1; +} + +ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + if(cf) + return cf->cft->do_recv(cf, data, buf, len, err); + *err = CURLE_RECV_ERROR; + return -1; +} + +CURLcode Curl_conn_connect(struct Curl_easy *data, + int sockindex, + bool blocking, + bool *done) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + + cf = data->conn->cfilter[sockindex]; + DEBUGASSERT(cf); + if(!cf) + return CURLE_FAILED_INIT; + + *done = cf->connected; + if(!*done) { + result = cf->cft->do_connect(cf, data, blocking, done); + if(!result && *done) { + Curl_conn_ev_update_info(data, data->conn); + conn_report_connect_stats(data, data->conn); + data->conn->keepalive = Curl_now(); + } + else if(result) { + conn_report_connect_stats(data, data->conn); + } + } + + return result; +} + +bool Curl_conn_is_connected(struct connectdata *conn, int sockindex) +{ + struct Curl_cfilter *cf; + + cf = conn->cfilter[sockindex]; + return cf && cf->connected; +} + +bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex) +{ + struct Curl_cfilter *cf; + + cf = data->conn->cfilter[sockindex]; + while(cf) { + if(cf->connected) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT) + return FALSE; + cf = cf->next; + } + return FALSE; +} + +bool Curl_conn_cf_is_ssl(struct Curl_cfilter *cf) +{ + for(; cf; cf = cf->next) { + if(cf->cft->flags & CF_TYPE_SSL) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT) + return FALSE; + } + return FALSE; +} + +bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex) +{ + return conn? Curl_conn_cf_is_ssl(conn->cfilter[sockindex]) : FALSE; +} + +bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex) +{ + struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + + for(; cf; cf = cf->next) { + if(cf->cft->flags & CF_TYPE_MULTIPLEX) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT + || cf->cft->flags & CF_TYPE_SSL) + return FALSE; + } + return FALSE; +} + +bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex) +{ + struct Curl_cfilter *cf; + + (void)data; + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + + cf = data->conn->cfilter[sockindex]; + while(cf && !cf->connected) { + cf = cf->next; + } + if(cf) { + return cf->cft->has_data_pending(cf, data); + } + return FALSE; +} + +void Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + /* Get the lowest not-connected filter, if there are any */ + while(cf && !cf->connected && cf->next && !cf->next->connected) + 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) { + cf->cft->adjust_pollset(cf, data, ps); + cf = cf->next; + } +} + +void Curl_conn_adjust_pollset(struct Curl_easy *data, + struct easy_pollset *ps) +{ + int i; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + for(i = 0; i < 2; ++i) { + Curl_conn_cf_adjust_pollset(data->conn->cfilter[i], data, ps); + } +} + +void Curl_conn_get_host(struct Curl_easy *data, int sockindex, + const char **phost, const char **pdisplay_host, + int *pport) +{ + struct Curl_cfilter *cf; + + DEBUGASSERT(data->conn); + cf = data->conn->cfilter[sockindex]; + if(cf) { + cf->cft->get_host(cf, data, phost, pdisplay_host, pport); + } + else { + /* Some filter ask during shutdown for this, mainly for debugging + * purposes. We hand out the defaults, however this is not always + * accurate, as the connection might be tunneled, etc. But all that + * state is already gone here. */ + *phost = data->conn->host.name; + *pdisplay_host = data->conn->host.dispname; + *pport = data->conn->remote_port; + } +} + +CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + (void)cf; + (void)data; + (void)event; + (void)arg1; + (void)arg2; + return CURLE_OK; +} + +CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool ignore_result, + int event, int arg1, void *arg2) +{ + CURLcode result = CURLE_OK; + + for(; cf; cf = cf->next) { + if(Curl_cf_def_cntrl == cf->cft->cntrl) + continue; + result = cf->cft->cntrl(cf, data, event, arg1, arg2); + if(!ignore_result && result) + break; + } + return result; +} + +curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + curl_socket_t sock; + if(cf && !cf->cft->query(cf, data, CF_QUERY_SOCKET, NULL, &sock)) + return sock; + return CURL_SOCKET_BAD; +} + +curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex) +{ + struct Curl_cfilter *cf; + + cf = data->conn? data->conn->cfilter[sockindex] : NULL; + /* if the top filter has not connected, ask it (and its sub-filters) + * for the socket. Otherwise conn->sock[sockindex] should have it. + */ + if(cf && !cf->connected) + return Curl_conn_cf_get_socket(cf, data); + return data->conn? data->conn->sock[sockindex] : CURL_SOCKET_BAD; +} + +void Curl_conn_forget_socket(struct Curl_easy *data, int sockindex) +{ + if(data->conn) { + struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; + if(cf) + (void)Curl_conn_cf_cntrl(cf, data, TRUE, + CF_CTRL_FORGET_SOCKET, 0, NULL); + fake_sclose(data->conn->sock[sockindex]); + data->conn->sock[sockindex] = CURL_SOCKET_BAD; + } +} + +static CURLcode cf_cntrl_all(struct connectdata *conn, + struct Curl_easy *data, + bool ignore_result, + int event, int arg1, void *arg2) +{ + CURLcode result = CURLE_OK; + size_t i; + + for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) { + result = Curl_conn_cf_cntrl(conn->cfilter[i], data, ignore_result, + event, arg1, arg2); + if(!ignore_result && result) + break; + } + return result; +} + +void Curl_conn_ev_data_attach(struct connectdata *conn, + struct Curl_easy *data) +{ + cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_ATTACH, 0, NULL); +} + +void Curl_conn_ev_data_detach(struct connectdata *conn, + struct Curl_easy *data) +{ + cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_DETACH, 0, NULL); +} + +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_ev_data_idle(struct Curl_easy *data) +{ + return cf_cntrl_all(data->conn, data, FALSE, + CF_CTRL_DATA_IDLE, 0, NULL); +} + +/** + * Notify connection filters that the transfer represented by `data` + * is donw with sending data (e.g. has uploaded everything). + */ +void Curl_conn_ev_data_done_send(struct Curl_easy *data) +{ + cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE_SEND, 0, NULL); +} + +/** + * Notify connection filters that the transfer represented by `data` + * is finished - eventually premature, e.g. before being complete. + */ +void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature) +{ + cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE, premature, NULL); +} + +CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause) +{ + return cf_cntrl_all(data->conn, data, FALSE, + CF_CTRL_DATA_PAUSE, do_pause, NULL); +} + +void Curl_conn_ev_update_info(struct Curl_easy *data, + struct connectdata *conn) +{ + cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_INFO_UPDATE, 0, NULL); +} + +/** + * Update connection statistics + */ +static void conn_report_connect_stats(struct Curl_easy *data, + struct connectdata *conn) +{ + struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET]; + 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); + } +} + +bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn, + bool *input_pending) +{ + struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET]; + return cf && !cf->conn->bits.close && + cf->cft->is_alive(cf, data, input_pending); +} + +CURLcode Curl_conn_keep_alive(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + return cf? cf->cft->keep_alive(cf, data) : CURLE_OK; +} + +size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + CURLcode result; + int n = 0; + + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + result = cf? cf->cft->query(cf, data, CF_QUERY_MAX_CONCURRENT, + &n, NULL) : CURLE_UNKNOWN_OPTION; + return (result || n <= 0)? 1 : (size_t)n; +} + + +void Curl_pollset_reset(struct Curl_easy *data, + struct easy_pollset *ps) +{ + size_t i; + (void)data; + memset(ps, 0, sizeof(*ps)); + for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) + ps->sockets[i] = CURL_SOCKET_BAD; +} + +/** + * + */ +void Curl_pollset_change(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + int add_flags, int remove_flags) +{ + unsigned int i; + + (void)data; + DEBUGASSERT(VALID_SOCK(sock)); + if(!VALID_SOCK(sock)) + return; + + DEBUGASSERT(add_flags <= (CURL_POLL_IN|CURL_POLL_OUT)); + DEBUGASSERT(remove_flags <= (CURL_POLL_IN|CURL_POLL_OUT)); + DEBUGASSERT((add_flags&remove_flags) == 0); /* no overlap */ + for(i = 0; i < ps->num; ++i) { + if(ps->sockets[i] == sock) { + ps->actions[i] &= (unsigned char)(~remove_flags); + ps->actions[i] |= (unsigned char)add_flags; + /* all gone? remove socket */ + if(!ps->actions[i]) { + if((i + 1) < ps->num) { + memmove(&ps->sockets[i], &ps->sockets[i + 1], + (ps->num - (i + 1)) * sizeof(ps->sockets[0])); + memmove(&ps->actions[i], &ps->actions[i + 1], + (ps->num - (i + 1)) * sizeof(ps->actions[0])); + } + --ps->num; + } + return; + } + } + /* not present */ + if(add_flags) { + /* Having more SOCKETS per easy handle than what is defined + * is a programming error. This indicates that we need + * to raise this limit, making easy_pollset larger. + * Since we use this in tight loops, we do not want to make + * the pollset dynamic unnecessarily. + * The current maximum in practise is HTTP/3 eyeballing where + * we have up to 4 sockets involved in connection setup. + */ + DEBUGASSERT(i < MAX_SOCKSPEREASYHANDLE); + if(i < MAX_SOCKSPEREASYHANDLE) { + ps->sockets[i] = sock; + ps->actions[i] = (unsigned char)add_flags; + ps->num = i + 1; + } + } +} + +void Curl_pollset_set(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool do_in, bool do_out) +{ + Curl_pollset_change(data, ps, sock, + (do_in?CURL_POLL_IN:0)|(do_out?CURL_POLL_OUT:0), + (!do_in?CURL_POLL_IN:0)|(!do_out?CURL_POLL_OUT:0)); +} + +static void ps_add(struct Curl_easy *data, struct easy_pollset *ps, + int bitmap, curl_socket_t *socks) +{ + if(bitmap) { + int i; + for(i = 0; i < MAX_SOCKSPEREASYHANDLE; ++i) { + if(!(bitmap & GETSOCK_MASK_RW(i)) || !VALID_SOCK((socks[i]))) { + break; + } + if(bitmap & GETSOCK_READSOCK(i)) { + if(bitmap & GETSOCK_WRITESOCK(i)) + Curl_pollset_add_inout(data, ps, socks[i]); + else + /* is READ, since we checked MASK_RW above */ + Curl_pollset_add_in(data, ps, socks[i]); + } + else + Curl_pollset_add_out(data, ps, socks[i]); + } + } +} + +void Curl_pollset_add_socks(struct Curl_easy *data, + struct easy_pollset *ps, + int (*get_socks_cb)(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks)) +{ + curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; + int bitmap; + + DEBUGASSERT(data->conn); + bitmap = get_socks_cb(data, data->conn, socks); + ps_add(data, ps, bitmap, socks); +} + +void Curl_pollset_add_socks2(struct Curl_easy *data, + struct easy_pollset *ps, + int (*get_socks_cb)(struct Curl_easy *data, + curl_socket_t *socks)) +{ + curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; + int bitmap; + + bitmap = get_socks_cb(data, socks); + ps_add(data, ps, bitmap, socks); +} + +void Curl_pollset_check(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool *pwant_read, bool *pwant_write) +{ + unsigned int i; + + (void)data; + DEBUGASSERT(VALID_SOCK(sock)); + for(i = 0; i < ps->num; ++i) { + if(ps->sockets[i] == sock) { + *pwant_read = !!(ps->actions[i] & CURL_POLL_IN); + *pwant_write = !!(ps->actions[i] & CURL_POLL_OUT); + return; + } + } + *pwant_read = *pwant_write = FALSE; +} diff --git a/Utilities/cmcurl/lib/cfilters.h b/Utilities/cmcurl/lib/cfilters.h new file mode 100644 index 0000000..09a3f16 --- /dev/null +++ b/Utilities/cmcurl/lib/cfilters.h @@ -0,0 +1,616 @@ +#ifndef HEADER_CURL_CFILTERS_H +#define HEADER_CURL_CFILTERS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + + +struct Curl_cfilter; +struct Curl_easy; +struct Curl_dns_entry; +struct connectdata; + +/* Callback to destroy resources held by this filter instance. + * Implementations MUST NOT chain calls to cf->next. + */ +typedef void Curl_cft_destroy_this(struct Curl_cfilter *cf, + struct Curl_easy *data); + +typedef void Curl_cft_close(struct Curl_cfilter *cf, + struct Curl_easy *data); + +typedef CURLcode Curl_cft_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done); + +/* Return the hostname and port the connection goes to. + * This may change with the connection state of filters when tunneling + * is involved. + * @param cf the filter to ask + * @param data the easy handle currently active + * @param phost on return, points to the relevant, real hostname. + * this is owned by the connection. + * @param pdisplay_host on return, points to the printable hostname. + * this is owned by the connection. + * @param pport on return, contains the port number + */ +typedef void Curl_cft_get_host(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char **phost, + const char **pdisplay_host, + int *pport); + +struct easy_pollset; + +/* Passing in an easy_pollset for monitoring of sockets, let + * filters add or remove sockets actions (CURL_POLL_OUT, CURL_POLL_IN). + * This may add a socket or, in case no actions remain, remove + * a socket from the set. + * + * Filter implementations need to call filters "below" *after* they have + * made their adjustments. This allows lower filters to override "upper" + * actions. If a "lower" filter is unable to write, it needs to be able + * to disallow POLL_OUT. + * + * A filter without own restrictions/preferences should not modify + * the pollset. Filters, whose filter "below" is not connected, should + * also do no adjustments. + * + * Examples: a TLS handshake, while ongoing, might remove POLL_IN + * when it needs to write, or vice versa. A HTTP/2 filter might remove + * POLL_OUT when a stream window is exhausted and a WINDOW_UPDATE needs + * to be received first and add instead POLL_IN. + * + * @param cf the filter to ask + * @param data the easy handle the pollset is about + * @param ps the pollset (inout) for the easy handle + */ +typedef void Curl_cft_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps); + +typedef bool Curl_cft_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data); + +typedef ssize_t Curl_cft_send(struct Curl_cfilter *cf, + struct Curl_easy *data, /* transfer */ + const void *buf, /* data to write */ + size_t len, /* amount to write */ + CURLcode *err); /* error to return */ + +typedef ssize_t Curl_cft_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, /* transfer */ + char *buf, /* store data here */ + size_t len, /* amount to read */ + CURLcode *err); /* error to return */ + +typedef bool Curl_cft_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending); + +typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data); + +/** + * Events/controls for connection filters, their arguments and + * return code handling. Filter callbacks are invoked "top down". + * Return code handling: + * "first fail" meaning that the first filter returning != CURLE_OK, will + * abort further event distribution and determine the result. + * "ignored" meaning return values are ignored and the event is distributed + * to all filters in the chain. Overall result is always CURLE_OK. + */ +/* data event arg1 arg2 return */ +#define CF_CTRL_DATA_ATTACH 1 /* 0 NULL ignored */ +#define CF_CTRL_DATA_DETACH 2 /* 0 NULL ignored */ +#define CF_CTRL_DATA_SETUP 4 /* 0 NULL first fail */ +#define CF_CTRL_DATA_IDLE 5 /* 0 NULL first fail */ +#define CF_CTRL_DATA_PAUSE 6 /* on/off NULL first fail */ +#define CF_CTRL_DATA_DONE 7 /* premature NULL ignored */ +#define CF_CTRL_DATA_DONE_SEND 8 /* 0 NULL ignored */ +/* update conn info at connection and data */ +#define CF_CTRL_CONN_INFO_UPDATE (256+0) /* 0 NULL ignored */ +#define CF_CTRL_FORGET_SOCKET (256+1) /* 0 NULL ignored */ + +/** + * Handle event/control for the filter. + * Implementations MUST NOT chain calls to cf->next. + */ +typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2); + + +/** + * Queries to ask via a `Curl_cft_query *query` method on a cfilter chain. + * - MAX_CONCURRENT: the maximum number of parallel transfers the filter + * chain expects to handle at the same time. + * default: 1 if no filter overrides. + * - CONNECT_REPLY_MS: milliseconds until the first indication of a server + * response was received on a connect. For TCP, this + * reflects the time until the socket connected. On UDP + * this gives the time the first bytes from the server + * were received. + * -1 if not determined yet. + * - CF_QUERY_SOCKET: the socket used by the filter chain + */ +/* 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 */ + +/** + * Query the cfilter for properties. Filters ignorant of a query will + * pass it "down" the filter chain. + */ +typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2); + +/** + * Type flags for connection filters. A filter can have none, one or + * many of those. Use to evaluate state/capabilities of a filter chain. + * + * CF_TYPE_IP_CONNECT: provides an IP connection or sth equivalent, like + * a CONNECT tunnel, a UNIX domain socket, a QUIC + * connection, etc. + * CF_TYPE_SSL: provide SSL/TLS + * CF_TYPE_MULTIPLEX: provides multiplexing of easy handles + */ +#define CF_TYPE_IP_CONNECT (1 << 0) +#define CF_TYPE_SSL (1 << 1) +#define CF_TYPE_MULTIPLEX (1 << 2) + +/* A connection filter type, e.g. specific implementation. */ +struct Curl_cftype { + const char *name; /* name of the filter type */ + int flags; /* flags of filter type */ + int log_level; /* log level for such filters */ + Curl_cft_destroy_this *destroy; /* destroy resources of this cf */ + Curl_cft_connect *do_connect; /* establish connection */ + Curl_cft_close *do_close; /* close conn */ + Curl_cft_get_host *get_host; /* host filter talks to */ + Curl_cft_adjust_pollset *adjust_pollset; /* adjust transfer poll set */ + Curl_cft_data_pending *has_data_pending;/* conn has data pending */ + Curl_cft_send *do_send; /* send data */ + Curl_cft_recv *do_recv; /* receive data */ + Curl_cft_cntrl *cntrl; /* events/control */ + Curl_cft_conn_is_alive *is_alive; /* FALSE if conn is dead, Jim! */ + Curl_cft_conn_keep_alive *keep_alive; /* try to keep it alive */ + Curl_cft_query *query; /* query filter chain */ +}; + +/* A connection filter instance, e.g. registered at a connection */ +struct Curl_cfilter { + const struct Curl_cftype *cft; /* the type providing implementation */ + 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 */ + BIT(connected); /* != 0 iff this filter is connected */ +}; + +/* Default implementations for the type functions, implementing nop. */ +void Curl_cf_def_destroy_this(struct Curl_cfilter *cf, + struct Curl_easy *data); + +/* Default implementations for the type functions, implementing pass-through + * the filter chain. */ +void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data, + const char **phost, const char **pdisplay_host, + int *pport); +void Curl_cf_def_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps); +bool Curl_cf_def_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data); +ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err); +ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err); +CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2); +bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending); +CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data); +CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2); + +/** + * Create a new filter instance, unattached to the filter chain. + * Use Curl_conn_cf_add() to add it to the chain. + * @param pcf on success holds the created instance + * @param cft the filter type + * @param ctx the type specific context to use + */ +CURLcode Curl_cf_create(struct Curl_cfilter **pcf, + const struct Curl_cftype *cft, + void *ctx); + +/** + * Add a filter instance to the `sockindex` filter chain at connection + * `conn`. The filter must not already be attached. It is inserted at + * the start of the chain (top). + */ +void Curl_conn_cf_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + struct Curl_cfilter *cf); + +/** + * Insert a filter (chain) after `cf_at`. + * `cf_new` must not already be attached. + */ +void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at, + struct Curl_cfilter *cf_new); + +/** + * Discard, e.g. remove and destroy `discard` iff + * it still is in the filter chain below `cf`. If `discard` + * is no longer found beneath `cf` return FALSE. + * if `destroy_always` is TRUE, will call `discard`s destroy + * function and free it even if not found in the subchain. + */ +bool Curl_conn_cf_discard_sub(struct Curl_cfilter *cf, + struct Curl_cfilter *discard, + struct Curl_easy *data, + bool destroy_always); + +/** + * Discard all cfilters starting with `*pcf` and clearing it afterwards. + */ +void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, + struct Curl_easy *data); + +/** + * 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); + + +CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done); +void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data); +ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err); +ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err); +CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool ignore_result, + int event, int arg1, void *arg2); + +/** + * Determine if the connection filter chain is using SSL to the remote host + * (or will be once connected). + */ +bool Curl_conn_cf_is_ssl(struct Curl_cfilter *cf); + +/** + * Get the socket used by the filter chain starting at `cf`. + * Returns CURL_SOCKET_BAD if not available. + */ +curl_socket_t Curl_conn_cf_get_socket(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 + +/** + * Bring the filter chain at `sockindex` for connection `data->conn` into + * connected state. Which will set `*done` to TRUE. + * This can be called on an already connected chain with no side effects. + * 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, + bool blocking, bool *done); + +/** + * Check if the filter chain at `sockindex` for connection `conn` is + * completely connected. + */ +bool Curl_conn_is_connected(struct connectdata *conn, int 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); + +/** + * 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); + +/** + * Connection provides multiplexing of easy handles at `socketindex`. + */ +bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex); + +/** + * Close the filter chain at `sockindex` for connection `data->conn`. + * Filters remain in place and may be connected again afterwards. + */ +void Curl_conn_close(struct Curl_easy *data, int sockindex); + +/** + * 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); + +/** + * Return the socket used on data's connection for the index. + * Returns CURL_SOCKET_BAD if not available. + */ +curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex); + +/** + * Tell filters to forget about the socket at sockindex. + */ +void Curl_conn_forget_socket(struct Curl_easy *data, int sockindex); + +/** + * Adjust the pollset for the filter chain startgin at `cf`. + */ +void Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps); + +/** + * Adjust pollset from filters installed at transfer's connection. + */ +void Curl_conn_adjust_pollset(struct Curl_easy *data, + struct easy_pollset *ps); + +/** + * Receive data through the filter chain at `sockindex` for connection + * `data->conn`. Copy at most `len` bytes into `buf`. Return the + * actuel number of bytes copied or a negative value on error. + * The error code is placed into `*code`. + */ +ssize_t Curl_conn_recv(struct Curl_easy *data, int sockindex, char *buf, + size_t len, CURLcode *code); + +/** + * Send `len` bytes of data from `buf` through the filter chain `sockindex` + * at connection `data->conn`. Return the actual number of bytes written + * or a negative value on error. + * The error code is placed into `*code`. + */ +ssize_t Curl_conn_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t len, CURLcode *code); + +/** + * The easy handle `data` is being attached to `conn`. This does + * not mean that data will actually do a transfer. Attachment is + * also used for temporary actions on the connection. + */ +void Curl_conn_ev_data_attach(struct connectdata *conn, + struct Curl_easy *data); + +/** + * The easy handle `data` is being detached (no longer served) + * by connection `conn`. All filters are informed to release any resources + * related to `data`. + * Note: there may be several `data` attached to a connection at the same + * time. + */ +void Curl_conn_ev_data_detach(struct connectdata *conn, + struct Curl_easy *data); + +/** + * Notify connection filters that they need to setup data for + * a transfer. + */ +CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data); + +/** + * Notify connection filters that now would be a good time to + * perform any idle, e.g. time related, actions. + */ +CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data); + +/** + * Notify connection filters that the transfer represented by `data` + * is donw with sending data (e.g. has uploaded everything). + */ +void Curl_conn_ev_data_done_send(struct Curl_easy *data); + +/** + * Notify connection filters that the transfer represented by `data` + * is finished - eventually premature, e.g. before being complete. + */ +void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature); + +/** + * Notify connection filters that the transfer of data is paused/unpaused. + */ +CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause); + +/** + * Inform connection filters to update their info in `conn`. + */ +void Curl_conn_ev_update_info(struct Curl_easy *data, + struct connectdata *conn); + +/** + * Check if FIRSTSOCKET's cfilter chain deems connection alive. + */ +bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn, + bool *input_pending); + +/** + * Try to upkeep the connection filters at sockindex. + */ +CURLcode Curl_conn_keep_alive(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + +void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data); +void Curl_conn_get_host(struct Curl_easy *data, int sockindex, + const char **phost, const char **pdisplay_host, + int *pport); + +/** + * Get the maximum number of parallel transfers the connection + * expects to be able to handle at `sockindex`. + */ +size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + + +void Curl_pollset_reset(struct Curl_easy *data, + struct easy_pollset *ps); + +/* Change the poll flags (CURL_POLL_IN/CURL_POLL_OUT) to the poll set for + * socket `sock`. If the socket is not already part of the poll set, it + * will be added. + * If the socket is present and all poll flags are cleared, it will be removed. + */ +void Curl_pollset_change(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + int add_flags, int remove_flags); + +void Curl_pollset_set(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool do_in, bool do_out); + +#define Curl_pollset_add_in(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), CURL_POLL_IN, 0) +#define Curl_pollset_add_out(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), CURL_POLL_OUT, 0) +#define Curl_pollset_add_inout(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), \ + CURL_POLL_IN|CURL_POLL_OUT, 0) +#define Curl_pollset_set_in_only(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), \ + CURL_POLL_IN, CURL_POLL_OUT) +#define Curl_pollset_set_out_only(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), \ + CURL_POLL_OUT, CURL_POLL_IN) + +void Curl_pollset_add_socks(struct Curl_easy *data, + struct easy_pollset *ps, + int (*get_socks_cb)(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks)); +void Curl_pollset_add_socks2(struct Curl_easy *data, + struct easy_pollset *ps, + int (*get_socks_cb)(struct Curl_easy *data, + curl_socket_t *socks)); + +/** + * Check if the pollset, as is, wants to read and/or write regarding + * the given socket. + */ +void Curl_pollset_check(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool *pwant_read, bool *pwant_write); + +/** + * Types and macros used to keep the current easy handle in filter calls, + * allowing for nested invocations. See #10336. + * + * `cf_call_data` is intended to be a member of the cfilter's `ctx` type. + * A filter defines the macro `CF_CTX_CALL_DATA` to give access to that. + * + * With all values 0, the default, this indicates that there is no cfilter + * call with `data` ongoing. + * Macro `CF_DATA_SAVE` preserves the current `cf_call_data` in a local + * variable and sets the `data` given, incrementing the `depth` counter. + * + * Macro `CF_DATA_RESTORE` restores the old values from the local variable, + * while checking that `depth` values are as expected (debug build), catching + * cases where a "lower" RESTORE was not called. + * + * Finally, macro `CF_DATA_CURRENT` gives the easy handle of the current + * invocation. + */ +struct cf_call_data { + struct Curl_easy *data; +#ifdef DEBUGBUILD + int depth; +#endif +}; + +/** + * define to access the `struct cf_call_data for a cfilter. Normally + * a member in the cfilter's `ctx`. + * + * #define CF_CTX_CALL_DATA(cf) -> struct cf_call_data instance +*/ + +#ifdef DEBUGBUILD + +#define CF_DATA_SAVE(save, cf, data) \ + do { \ + (save) = CF_CTX_CALL_DATA(cf); \ + DEBUGASSERT((save).data == NULL || (save).depth > 0); \ + CF_CTX_CALL_DATA(cf).depth++; \ + CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \ + } while(0) + +#define CF_DATA_RESTORE(cf, save) \ + do { \ + DEBUGASSERT(CF_CTX_CALL_DATA(cf).depth == (save).depth + 1); \ + DEBUGASSERT((save).data == NULL || (save).depth > 0); \ + CF_CTX_CALL_DATA(cf) = (save); \ + } while(0) + +#else /* DEBUGBUILD */ + +#define CF_DATA_SAVE(save, cf, data) \ + do { \ + (save) = CF_CTX_CALL_DATA(cf); \ + CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \ + } while(0) + +#define CF_DATA_RESTORE(cf, save) \ + do { \ + CF_CTX_CALL_DATA(cf) = (save); \ + } while(0) + +#endif /* !DEBUGBUILD */ + +#define CF_DATA_CURRENT(cf) \ + ((cf)? (CF_CTX_CALL_DATA(cf).data) : NULL) + +#endif /* HEADER_CURL_CFILTERS_H */ diff --git a/Utilities/cmcurl/lib/conncache.c b/Utilities/cmcurl/lib/conncache.c new file mode 100644 index 0000000..66f18ec --- /dev/null +++ b/Utilities/cmcurl/lib/conncache.c @@ -0,0 +1,588 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "urldata.h" +#include "url.h" +#include "progress.h" +#include "multiif.h" +#include "sendf.h" +#include "conncache.h" +#include "share.h" +#include "sigpipe.h" +#include "connect.h" +#include "strcase.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define HASHKEY_SIZE 128 + +static CURLcode bundle_create(struct connectbundle **bundlep) +{ + DEBUGASSERT(*bundlep == NULL); + *bundlep = malloc(sizeof(struct connectbundle)); + if(!*bundlep) + return CURLE_OUT_OF_MEMORY; + + (*bundlep)->num_connections = 0; + (*bundlep)->multiuse = BUNDLE_UNKNOWN; + + Curl_llist_init(&(*bundlep)->conn_list, NULL); + return CURLE_OK; +} + +static void bundle_destroy(struct connectbundle *bundle) +{ + free(bundle); +} + +/* Add a connection to a bundle */ +static void bundle_add_conn(struct connectbundle *bundle, + struct connectdata *conn) +{ + Curl_llist_insert_next(&bundle->conn_list, bundle->conn_list.tail, conn, + &conn->bundle_node); + conn->bundle = bundle; + bundle->num_connections++; +} + +/* Remove a connection from a bundle */ +static int bundle_remove_conn(struct connectbundle *bundle, + struct connectdata *conn) +{ + struct Curl_llist_element *curr; + + curr = bundle->conn_list.head; + while(curr) { + if(curr->ptr == conn) { + Curl_llist_remove(&bundle->conn_list, curr, NULL); + bundle->num_connections--; + conn->bundle = NULL; + return 1; /* we removed a handle */ + } + curr = curr->next; + } + DEBUGASSERT(0); + return 0; +} + +static void free_bundle_hash_entry(void *freethis) +{ + struct connectbundle *b = (struct connectbundle *) freethis; + + bundle_destroy(b); +} + +int Curl_conncache_init(struct conncache *connc, int size) +{ + /* allocate a new easy handle to use when closing cached connections */ + connc->closure_handle = curl_easy_init(); + if(!connc->closure_handle) + return 1; /* bad */ + connc->closure_handle->state.internal = true; + + Curl_hash_init(&connc->hash, size, Curl_hash_str, + Curl_str_key_compare, free_bundle_hash_entry); + connc->closure_handle->state.conn_cache = connc; + + return 0; /* good */ +} + +void Curl_conncache_destroy(struct conncache *connc) +{ + if(connc) + Curl_hash_destroy(&connc->hash); +} + +/* creates a key to find a bundle for this connection */ +static void hashkey(struct connectdata *conn, char *buf, size_t len) +{ + const char *hostname; + long port = conn->remote_port; + DEBUGASSERT(len >= HASHKEY_SIZE); +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + hostname = conn->http_proxy.host.name; + port = conn->port; + } + else +#endif + if(conn->bits.conn_to_host) + hostname = conn->conn_to_host.name; + else + hostname = conn->host.name; + + /* put the numbers first so that the hostname gets cut off if too long */ +#ifdef ENABLE_IPV6 + msnprintf(buf, len, "%u/%ld/%s", conn->scope_id, port, hostname); +#else + msnprintf(buf, len, "%ld/%s", port, hostname); +#endif + Curl_strntolower(buf, buf, len); +} + +/* Returns number of connections currently held in the connection cache. + Locks/unlocks the cache itself! +*/ +size_t Curl_conncache_size(struct Curl_easy *data) +{ + size_t num; + CONNCACHE_LOCK(data); + num = data->state.conn_cache->num_conn; + CONNCACHE_UNLOCK(data); + return num; +} + +/* Look up the bundle with all the connections to the same host this + connectdata struct is setup to use. + + **NOTE**: When it returns, it holds the connection cache lock! */ +struct connectbundle * +Curl_conncache_find_bundle(struct Curl_easy *data, + struct connectdata *conn, + struct conncache *connc) +{ + struct connectbundle *bundle = NULL; + CONNCACHE_LOCK(data); + if(connc) { + char key[HASHKEY_SIZE]; + hashkey(conn, key, sizeof(key)); + bundle = Curl_hash_pick(&connc->hash, key, strlen(key)); + } + + return bundle; +} + +static void *conncache_add_bundle(struct conncache *connc, + char *key, + struct connectbundle *bundle) +{ + return Curl_hash_add(&connc->hash, key, strlen(key), bundle); +} + +static void conncache_remove_bundle(struct conncache *connc, + struct connectbundle *bundle) +{ + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + + if(!connc) + return; + + Curl_hash_start_iterate(&connc->hash, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + if(he->ptr == bundle) { + /* The bundle is destroyed by the hash destructor function, + free_bundle_hash_entry() */ + Curl_hash_delete(&connc->hash, he->key, he->key_len); + return; + } + + he = Curl_hash_next_element(&iter); + } +} + +CURLcode Curl_conncache_add_conn(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct connectbundle *bundle = NULL; + struct connectdata *conn = data->conn; + struct conncache *connc = data->state.conn_cache; + DEBUGASSERT(conn); + + /* *find_bundle() locks the connection cache */ + bundle = Curl_conncache_find_bundle(data, conn, data->state.conn_cache); + if(!bundle) { + char key[HASHKEY_SIZE]; + + result = bundle_create(&bundle); + if(result) { + goto unlock; + } + + hashkey(conn, key, sizeof(key)); + + if(!conncache_add_bundle(data->state.conn_cache, key, bundle)) { + bundle_destroy(bundle); + result = CURLE_OUT_OF_MEMORY; + goto unlock; + } + } + + bundle_add_conn(bundle, conn); + conn->connection_id = connc->next_connection_id++; + connc->num_conn++; + + DEBUGF(infof(data, "Added connection %" CURL_FORMAT_CURL_OFF_T ". " + "The cache now contains %zu members", + conn->connection_id, connc->num_conn)); + +unlock: + CONNCACHE_UNLOCK(data); + + return result; +} + +/* + * Removes the connectdata object from the connection cache, but the transfer + * still owns this connection. + * + * Pass TRUE/FALSE in the 'lock' argument depending on if the parent function + * already holds the lock or not. + */ +void Curl_conncache_remove_conn(struct Curl_easy *data, + struct connectdata *conn, bool lock) +{ + struct connectbundle *bundle = conn->bundle; + struct conncache *connc = data->state.conn_cache; + + /* The bundle pointer can be NULL, since this function can be called + due to a failed connection attempt, before being added to a bundle */ + if(bundle) { + if(lock) { + CONNCACHE_LOCK(data); + } + bundle_remove_conn(bundle, conn); + if(bundle->num_connections == 0) + conncache_remove_bundle(connc, bundle); + conn->bundle = NULL; /* removed from it */ + if(connc) { + connc->num_conn--; + DEBUGF(infof(data, "The cache now contains %zu members", + connc->num_conn)); + } + if(lock) { + CONNCACHE_UNLOCK(data); + } + } +} + +/* This function iterates the entire connection cache and calls the function + func() with the connection pointer as the first argument and the supplied + 'param' argument as the other. + + The conncache lock is still held when the callback is called. It needs it, + so that it can safely continue traversing the lists once the callback + returns. + + Returns 1 if the loop was aborted due to the callback's return code. + + Return 0 from func() to continue the loop, return 1 to abort it. + */ +bool Curl_conncache_foreach(struct Curl_easy *data, + struct conncache *connc, + void *param, + int (*func)(struct Curl_easy *data, + struct connectdata *conn, void *param)) +{ + struct Curl_hash_iterator iter; + struct Curl_llist_element *curr; + struct Curl_hash_element *he; + + if(!connc) + return FALSE; + + CONNCACHE_LOCK(data); + Curl_hash_start_iterate(&connc->hash, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + struct connectbundle *bundle; + + bundle = he->ptr; + he = Curl_hash_next_element(&iter); + + curr = bundle->conn_list.head; + while(curr) { + /* Yes, we need to update curr before calling func(), because func() + might decide to remove the connection */ + struct connectdata *conn = curr->ptr; + curr = curr->next; + + if(1 == func(data, conn, param)) { + CONNCACHE_UNLOCK(data); + return TRUE; + } + } + } + CONNCACHE_UNLOCK(data); + return FALSE; +} + +/* Return the first connection found in the cache. Used when closing all + connections. + + NOTE: no locking is done here as this is presumably only done when cleaning + up a cache! +*/ +static struct connectdata * +conncache_find_first_connection(struct conncache *connc) +{ + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + struct connectbundle *bundle; + + Curl_hash_start_iterate(&connc->hash, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + struct Curl_llist_element *curr; + bundle = he->ptr; + + curr = bundle->conn_list.head; + if(curr) { + return curr->ptr; + } + + he = Curl_hash_next_element(&iter); + } + + return NULL; +} + +/* + * Give ownership of a connection back to the connection cache. Might + * disconnect the oldest existing in there to make space. + * + * Return TRUE if stored, FALSE if closed. + */ +bool Curl_conncache_return_conn(struct Curl_easy *data, + struct connectdata *conn) +{ + unsigned int maxconnects = !data->multi->maxconnects ? + data->multi->num_easy * 4: data->multi->maxconnects; + struct connectdata *conn_candidate = NULL; + + conn->lastused = Curl_now(); /* it was used up until now */ + if(maxconnects && Curl_conncache_size(data) > maxconnects) { + infof(data, "Connection cache is full, closing the oldest one"); + + conn_candidate = Curl_conncache_extract_oldest(data); + if(conn_candidate) { + /* Use the closure handle for this disconnect so that anything that + happens during the disconnect is not stored and associated with the + 'data' handle which already just finished a transfer and it is + important that details from this (unrelated) disconnect does not + taint meta-data in the data handle. */ + struct conncache *connc = data->state.conn_cache; + connc->closure_handle->state.buffer = data->state.buffer; + connc->closure_handle->set.buffer_size = data->set.buffer_size; + Curl_disconnect(connc->closure_handle, conn_candidate, + /* dead_connection */ FALSE); + } + } + + return (conn_candidate == conn) ? FALSE : TRUE; + +} + +/* + * This function finds the connection in the connection bundle that has been + * unused for the longest time. + * + * Does not lock the connection cache! + * + * Returns the pointer to the oldest idle connection, or NULL if none was + * found. + */ +struct connectdata * +Curl_conncache_extract_bundle(struct Curl_easy *data, + struct connectbundle *bundle) +{ + struct Curl_llist_element *curr; + timediff_t highscore = -1; + timediff_t score; + struct curltime now; + struct connectdata *conn_candidate = NULL; + struct connectdata *conn; + + (void)data; + + now = Curl_now(); + + curr = bundle->conn_list.head; + while(curr) { + conn = curr->ptr; + + if(!CONN_INUSE(conn)) { + /* Set higher score for the age passed since the connection was used */ + score = Curl_timediff(now, conn->lastused); + + if(score > highscore) { + highscore = score; + conn_candidate = conn; + } + } + curr = curr->next; + } + if(conn_candidate) { + /* remove it to prevent another thread from nicking it */ + bundle_remove_conn(bundle, conn_candidate); + data->state.conn_cache->num_conn--; + DEBUGF(infof(data, "The cache now contains %zu members", + data->state.conn_cache->num_conn)); + } + + return conn_candidate; +} + +/* + * This function finds the connection in the connection cache that has been + * unused for the longest time and extracts that from the bundle. + * + * Returns the pointer to the connection, or NULL if none was found. + */ +struct connectdata * +Curl_conncache_extract_oldest(struct Curl_easy *data) +{ + struct conncache *connc = data->state.conn_cache; + struct Curl_hash_iterator iter; + struct Curl_llist_element *curr; + struct Curl_hash_element *he; + timediff_t highscore =- 1; + timediff_t score; + struct curltime now; + struct connectdata *conn_candidate = NULL; + struct connectbundle *bundle; + struct connectbundle *bundle_candidate = NULL; + + now = Curl_now(); + + CONNCACHE_LOCK(data); + Curl_hash_start_iterate(&connc->hash, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + struct connectdata *conn; + + bundle = he->ptr; + + curr = bundle->conn_list.head; + while(curr) { + conn = curr->ptr; + + if(!CONN_INUSE(conn) && !conn->bits.close && + !conn->connect_only) { + /* Set higher score for the age passed since the connection was used */ + score = Curl_timediff(now, conn->lastused); + + if(score > highscore) { + highscore = score; + conn_candidate = conn; + bundle_candidate = bundle; + } + } + curr = curr->next; + } + + he = Curl_hash_next_element(&iter); + } + if(conn_candidate) { + /* remove it to prevent another thread from nicking it */ + bundle_remove_conn(bundle_candidate, conn_candidate); + connc->num_conn--; + DEBUGF(infof(data, "The cache now contains %zu members", + connc->num_conn)); + } + CONNCACHE_UNLOCK(data); + + return conn_candidate; +} + +void Curl_conncache_close_all_connections(struct conncache *connc) +{ + struct connectdata *conn; + char buffer[READBUFFER_MIN + 1]; + SIGPIPE_VARIABLE(pipe_st); + if(!connc->closure_handle) + return; + connc->closure_handle->state.buffer = buffer; + connc->closure_handle->set.buffer_size = READBUFFER_MIN; + + conn = conncache_find_first_connection(connc); + while(conn) { + sigpipe_ignore(connc->closure_handle, &pipe_st); + /* This will remove the connection from the cache */ + connclose(conn, "kill all"); + Curl_conncache_remove_conn(connc->closure_handle, conn, TRUE); + Curl_disconnect(connc->closure_handle, conn, FALSE); + sigpipe_restore(&pipe_st); + + conn = conncache_find_first_connection(connc); + } + + connc->closure_handle->state.buffer = NULL; + sigpipe_ignore(connc->closure_handle, &pipe_st); + + Curl_hostcache_clean(connc->closure_handle, + connc->closure_handle->dns.hostcache); + Curl_close(&connc->closure_handle); + sigpipe_restore(&pipe_st); +} + +#if 0 +/* Useful for debugging the connection cache */ +void Curl_conncache_print(struct conncache *connc) +{ + struct Curl_hash_iterator iter; + struct Curl_llist_element *curr; + struct Curl_hash_element *he; + + if(!connc) + return; + + fprintf(stderr, "=Bundle cache=\n"); + + Curl_hash_start_iterate(connc->hash, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + struct connectbundle *bundle; + struct connectdata *conn; + + bundle = he->ptr; + + fprintf(stderr, "%s -", he->key); + curr = bundle->conn_list->head; + while(curr) { + conn = curr->ptr; + + fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse); + curr = curr->next; + } + fprintf(stderr, "\n"); + + he = Curl_hash_next_element(&iter); + } +} +#endif diff --git a/Utilities/cmcurl/lib/conncache.h b/Utilities/cmcurl/lib/conncache.h new file mode 100644 index 0000000..c60f844 --- /dev/null +++ b/Utilities/cmcurl/lib/conncache.h @@ -0,0 +1,122 @@ +#ifndef HEADER_CURL_CONNCACHE_H +#define HEADER_CURL_CONNCACHE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se> + * + * 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 + * + ***************************************************************************/ + +/* + * All accesses to struct fields and changing of data in the connection cache + * and connectbundles must be done with the conncache LOCKED. The cache might + * be shared. + */ + +#include <curl/curl.h> +#include "timeval.h" + +struct connectdata; + +struct conncache { + struct Curl_hash hash; + size_t num_conn; + curl_off_t next_connection_id; + curl_off_t next_easy_id; + struct curltime last_cleanup; + /* handle used for closing cached connections */ + struct Curl_easy *closure_handle; +}; + +#define BUNDLE_NO_MULTIUSE -1 +#define BUNDLE_UNKNOWN 0 /* initial value */ +#define BUNDLE_MULTIPLEX 2 + +#ifdef CURLDEBUG +/* the debug versions of these macros make extra certain that the lock is + never doubly locked or unlocked */ +#define CONNCACHE_LOCK(x) \ + do { \ + if((x)->share) { \ + Curl_share_lock((x), CURL_LOCK_DATA_CONNECT, \ + CURL_LOCK_ACCESS_SINGLE); \ + DEBUGASSERT(!(x)->state.conncache_lock); \ + (x)->state.conncache_lock = TRUE; \ + } \ + } while(0) + +#define CONNCACHE_UNLOCK(x) \ + do { \ + if((x)->share) { \ + DEBUGASSERT((x)->state.conncache_lock); \ + (x)->state.conncache_lock = FALSE; \ + Curl_share_unlock((x), CURL_LOCK_DATA_CONNECT); \ + } \ + } while(0) +#else +#define CONNCACHE_LOCK(x) if((x)->share) \ + Curl_share_lock((x), CURL_LOCK_DATA_CONNECT, CURL_LOCK_ACCESS_SINGLE) +#define CONNCACHE_UNLOCK(x) if((x)->share) \ + Curl_share_unlock((x), CURL_LOCK_DATA_CONNECT) +#endif + +struct connectbundle { + int multiuse; /* supports multi-use */ + size_t num_connections; /* Number of connections in the bundle */ + struct Curl_llist conn_list; /* The connectdata members of the bundle */ +}; + +/* returns 1 on error, 0 is fine */ +int Curl_conncache_init(struct conncache *, int size); +void Curl_conncache_destroy(struct conncache *connc); + +/* return the correct bundle, to a host or a proxy */ +struct connectbundle *Curl_conncache_find_bundle(struct Curl_easy *data, + struct connectdata *conn, + struct conncache *connc); +/* returns number of connections currently held in the connection cache */ +size_t Curl_conncache_size(struct Curl_easy *data); + +bool Curl_conncache_return_conn(struct Curl_easy *data, + struct connectdata *conn); +CURLcode Curl_conncache_add_conn(struct Curl_easy *data) WARN_UNUSED_RESULT; +void Curl_conncache_remove_conn(struct Curl_easy *data, + struct connectdata *conn, + bool lock); +bool Curl_conncache_foreach(struct Curl_easy *data, + struct conncache *connc, + void *param, + int (*func)(struct Curl_easy *data, + struct connectdata *conn, + void *param)); + +struct connectdata * +Curl_conncache_find_first_connection(struct conncache *connc); + +struct connectdata * +Curl_conncache_extract_bundle(struct Curl_easy *data, + struct connectbundle *bundle); +struct connectdata * +Curl_conncache_extract_oldest(struct Curl_easy *data); +void Curl_conncache_close_all_connections(struct conncache *connc); +void Curl_conncache_print(struct conncache *connc); + +#endif /* HEADER_CURL_CONNCACHE_H */ diff --git a/Utilities/cmcurl/lib/connect.c b/Utilities/cmcurl/lib/connect.c new file mode 100644 index 0000000..ec5ab71 --- /dev/null +++ b/Utilities/cmcurl/lib/connect.c @@ -0,0 +1,1455 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> /* <netinet/tcp.h> may need it */ +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> /* for sockaddr_un */ +#endif +#ifdef HAVE_LINUX_TCP_H +#include <linux/tcp.h> +#elif defined(HAVE_NETINET_TCP_H) +#include <netinet/tcp.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include "urldata.h" +#include "sendf.h" +#include "if2ip.h" +#include "strerror.h" +#include "cfilters.h" +#include "connect.h" +#include "cf-haproxy.h" +#include "cf-https-connect.h" +#include "cf-socket.h" +#include "select.h" +#include "url.h" /* for Curl_safefree() */ +#include "multiif.h" +#include "sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "inet_ntop.h" +#include "inet_pton.h" +#include "vtls/vtls.h" /* for vtsl cfilters */ +#include "progress.h" +#include "warnless.h" +#include "conncache.h" +#include "multihandle.h" +#include "share.h" +#include "version_win32.h" +#include "vquic/vquic.h" /* for quic cfilters */ +#include "http_proxy.h" +#include "socks.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +/* + * Curl_timeleft() returns the amount of milliseconds left allowed for the + * transfer/connection. If the value is 0, there's no timeout (ie there's + * infinite time left). If the value is negative, the timeout time has already + * elapsed. + * + * If 'nowp' is non-NULL, it points to the current time. + * 'duringconnect' is FALSE if not during a connect, as then of course the + * connect timeout is not taken into account! + * + * @unittest: 1303 + */ + +#define TIMEOUT_CONNECT 1 +#define TIMEOUT_MAXTIME 2 + +timediff_t Curl_timeleft(struct Curl_easy *data, + struct curltime *nowp, + bool duringconnect) +{ + unsigned int timeout_set = 0; + timediff_t connect_timeout_ms = 0; + timediff_t maxtime_timeout_ms = 0; + timediff_t timeout_ms = 0; + struct curltime now; + + /* The duration of a connect and the total transfer are calculated from two + different time-stamps. It can end up with the total timeout being reached + before the connect timeout expires and we must acknowledge whichever + timeout that is reached first. The total timeout is set per entire + operation, while the connect timeout is set per connect. */ + + if(data->set.timeout > 0) { + timeout_set = TIMEOUT_MAXTIME; + maxtime_timeout_ms = data->set.timeout; + } + if(duringconnect) { + timeout_set |= TIMEOUT_CONNECT; + connect_timeout_ms = (data->set.connecttimeout > 0) ? + data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT; + } + if(!timeout_set) + /* no timeout */ + return 0; + + if(!nowp) { + now = Curl_now(); + nowp = &now; + } + + if(timeout_set & TIMEOUT_MAXTIME) { + maxtime_timeout_ms -= Curl_timediff(*nowp, data->progress.t_startop); + timeout_ms = maxtime_timeout_ms; + } + + if(timeout_set & TIMEOUT_CONNECT) { + connect_timeout_ms -= Curl_timediff(*nowp, data->progress.t_startsingle); + + if(!(timeout_set & TIMEOUT_MAXTIME) || + (connect_timeout_ms < maxtime_timeout_ms)) + timeout_ms = connect_timeout_ms; + } + + if(!timeout_ms) + /* avoid returning 0 as that means no timeout! */ + return -1; + + return timeout_ms; +} + +/* Copies connection info into the transfer handle to make it available when + the transfer handle is no longer associated with the connection. */ +void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn, + char *local_ip, int local_port) +{ + memcpy(data->info.conn_primary_ip, conn->primary_ip, MAX_IPADR_LEN); + if(local_ip && local_ip[0]) + memcpy(data->info.conn_local_ip, local_ip, MAX_IPADR_LEN); + else + data->info.conn_local_ip[0] = 0; + data->info.conn_scheme = conn->handler->scheme; + /* conn_protocol can only provide "old" protocols */ + data->info.conn_protocol = (conn->handler->protocol) & CURLPROTO_MASK; + data->info.conn_primary_port = conn->port; + data->info.conn_remote_port = conn->remote_port; + data->info.conn_local_port = local_port; +} + +static const struct Curl_addrinfo * +addr_first_match(const struct Curl_addrinfo *addr, int family) +{ + while(addr) { + if(addr->ai_family == family) + return addr; + addr = addr->ai_next; + } + return NULL; +} + +static const struct Curl_addrinfo * +addr_next_match(const struct Curl_addrinfo *addr, int family) +{ + while(addr && addr->ai_next) { + addr = addr->ai_next; + if(addr->ai_family == family) + return addr; + } + return NULL; +} + +/* retrieves ip address and port from a sockaddr structure. + note it calls Curl_inet_ntop which sets errno on fail, not SOCKERRNO. */ +bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, + char *addr, int *port) +{ + struct sockaddr_in *si = NULL; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *si6 = NULL; +#endif +#if (defined(HAVE_SYS_UN_H) || defined(WIN32_SOCKADDR_UN)) && defined(AF_UNIX) + struct sockaddr_un *su = NULL; +#else + (void)salen; +#endif + + switch(sa->sa_family) { + case AF_INET: + si = (struct sockaddr_in *)(void *) sa; + if(Curl_inet_ntop(sa->sa_family, &si->sin_addr, + addr, MAX_IPADR_LEN)) { + unsigned short us_port = ntohs(si->sin_port); + *port = us_port; + return TRUE; + } + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + si6 = (struct sockaddr_in6 *)(void *) sa; + if(Curl_inet_ntop(sa->sa_family, &si6->sin6_addr, + addr, MAX_IPADR_LEN)) { + unsigned short us_port = ntohs(si6->sin6_port); + *port = us_port; + return TRUE; + } + break; +#endif +#if (defined(HAVE_SYS_UN_H) || defined(WIN32_SOCKADDR_UN)) && defined(AF_UNIX) + case AF_UNIX: + if(salen > (curl_socklen_t)sizeof(CURL_SA_FAMILY_T)) { + su = (struct sockaddr_un*)sa; + msnprintf(addr, MAX_IPADR_LEN, "%s", su->sun_path); + } + else + addr[0] = 0; /* socket with no name */ + *port = 0; + return TRUE; +#endif + default: + break; + } + + addr[0] = '\0'; + *port = 0; + errno = EAFNOSUPPORT; + return FALSE; +} + +struct connfind { + curl_off_t id_tofind; + struct connectdata *found; +}; + +static int conn_is_conn(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + struct connfind *f = (struct connfind *)param; + (void)data; + if(conn->connection_id == f->id_tofind) { + f->found = conn; + return 1; + } + return 0; +} + +/* + * Used to extract socket and connectdata struct for the most recent + * transfer on the given Curl_easy. + * + * The returned socket will be CURL_SOCKET_BAD in case of failure! + */ +curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, + struct connectdata **connp) +{ + DEBUGASSERT(data); + + /* this works for an easy handle: + * - that has been used for curl_easy_perform() + * - that is associated with a multi handle, and whose connection + * was detached with CURLOPT_CONNECT_ONLY + */ + if((data->state.lastconnect_id != -1) && (data->multi_easy || data->multi)) { + struct connectdata *c; + struct connfind find; + find.id_tofind = data->state.lastconnect_id; + find.found = NULL; + + Curl_conncache_foreach(data, + data->share && (data->share->specifier + & (1<< CURL_LOCK_DATA_CONNECT))? + &data->share->conn_cache: + data->multi_easy? + &data->multi_easy->conn_cache: + &data->multi->conn_cache, &find, conn_is_conn); + + if(!find.found) { + data->state.lastconnect_id = -1; + return CURL_SOCKET_BAD; + } + + c = find.found; + if(connp) + /* only store this if the caller cares for it */ + *connp = c; + return c->sock[FIRSTSOCKET]; + } + 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(CURL_DISABLE_VERBOSE_STRINGS) + , const char *reason +#endif + ) +{ + /* close if a connection, or a stream that isn't 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(CURL_DISABLE_VERBOSE_STRINGS) + (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((bit)closeit != conn->bits.close) { + conn->bits.close = closeit; /* the only place in the source code that + should assign this bit */ + } +} + +/** + * job walking the matching addr infos, creating a sub-cfilter with the + * provided method `cf_create` and running setup/connect on it. + */ +struct eyeballer { + const char *name; + const struct Curl_addrinfo *first; /* complete address list, not owned */ + const struct Curl_addrinfo *addr; /* List of addresses to try, not owned */ + int ai_family; /* matching address family only */ + cf_ip_connect_create *cf_create; /* for creating cf */ + struct Curl_cfilter *cf; /* current sub-cfilter connecting */ + struct eyeballer *primary; /* eyeballer this one is backup for */ + timediff_t delay_ms; /* delay until start */ + struct curltime started; /* start of current attempt */ + timediff_t timeoutms; /* timeout for current attempt */ + expire_id timeout_id; /* ID for Curl_expire() */ + CURLcode result; + int error; + BIT(rewinded); /* if we rewinded the addr list */ + BIT(has_started); /* attempts have started */ + BIT(is_done); /* out of addresses/time */ + BIT(connected); /* cf has connected */ + BIT(inconclusive); /* connect was not a hard failure, we + * might talk to a restarting server */ +}; + + +typedef enum { + SCFST_INIT, + SCFST_WAITING, + SCFST_DONE +} cf_connect_state; + +struct cf_he_ctx { + int transport; + cf_ip_connect_create *cf_create; + const struct Curl_dns_entry *remotehost; + cf_connect_state state; + struct eyeballer *baller[2]; + struct eyeballer *winner; + struct curltime started; +}; + +/* when there are more than one IP address left to use, this macro returns how + much of the given timeout to spend on *this* attempt */ +#define TIMEOUT_LARGE 600 +#define USETIME(ms) ((ms > TIMEOUT_LARGE) ? (ms / 2) : ms) + +static CURLcode eyeballer_new(struct eyeballer **pballer, + cf_ip_connect_create *cf_create, + const struct Curl_addrinfo *addr, + int ai_family, + struct eyeballer *primary, + timediff_t delay_ms, + timediff_t timeout_ms, + expire_id timeout_id) +{ + struct eyeballer *baller; + + *pballer = NULL; + baller = calloc(1, sizeof(*baller) + 1000); + if(!baller) + return CURLE_OUT_OF_MEMORY; + + baller->name = ((ai_family == AF_INET)? "ipv4" : ( +#ifdef ENABLE_IPV6 + (ai_family == AF_INET6)? "ipv6" : +#endif + "ip")); + baller->cf_create = cf_create; + baller->first = baller->addr = addr; + baller->ai_family = ai_family; + baller->primary = primary; + baller->delay_ms = delay_ms; + baller->timeoutms = addr_next_match(baller->addr, baller->ai_family)? + USETIME(timeout_ms) : timeout_ms; + baller->timeout_id = timeout_id; + baller->result = CURLE_COULDNT_CONNECT; + + *pballer = baller; + return CURLE_OK; +} + +static void baller_close(struct eyeballer *baller, + struct Curl_easy *data) +{ + if(baller && baller->cf) { + Curl_conn_cf_discard_chain(&baller->cf, data); + } +} + +static void baller_free(struct eyeballer *baller, + struct Curl_easy *data) +{ + if(baller) { + baller_close(baller, data); + free(baller); + } +} + +static void baller_rewind(struct eyeballer *baller) +{ + baller->rewinded = TRUE; + baller->addr = baller->first; + baller->inconclusive = FALSE; +} + +static void baller_next_addr(struct eyeballer *baller) +{ + baller->addr = addr_next_match(baller->addr, baller->ai_family); +} + +/* + * Initiate a connect attempt walk. + * + * Note that even on connect fail it returns CURLE_OK, but with 'sock' set to + * CURL_SOCKET_BAD. Other errors will however return proper errors. + */ +static void baller_initiate(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct eyeballer *baller) +{ + struct cf_he_ctx *ctx = cf->ctx; + struct Curl_cfilter *cf_prev = baller->cf; + struct Curl_cfilter *wcf; + CURLcode result; + + + /* Don't close a previous cfilter yet to ensure that the next IP's + socket gets a different file descriptor, which can prevent bugs when + the curl_multi_socket_action interface is used with certain select() + replacements such as kqueue. */ + result = baller->cf_create(&baller->cf, data, cf->conn, baller->addr, + ctx->transport); + if(result) + goto out; + + /* the new filter might have sub-filters */ + for(wcf = baller->cf; wcf; wcf = wcf->next) { + wcf->conn = cf->conn; + wcf->sockindex = cf->sockindex; + } + + if(addr_next_match(baller->addr, baller->ai_family)) { + Curl_expire(data, baller->timeoutms, baller->timeout_id); + } + +out: + if(result) { + CURL_TRC_CF(data, cf, "%s failed", baller->name); + baller_close(baller, data); + } + if(cf_prev) + Curl_conn_cf_discard_chain(&cf_prev, data); + baller->result = result; +} + +/** + * Start a connection attempt on the current baller address. + * Will return CURLE_OK on the first address where a socket + * could be created and the non-blocking connect started. + * Returns error when all remaining addresses have been tried. + */ +static CURLcode baller_start(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct eyeballer *baller, + timediff_t timeoutms) +{ + baller->error = 0; + baller->connected = FALSE; + baller->has_started = TRUE; + + while(baller->addr) { + baller->started = Curl_now(); + baller->timeoutms = addr_next_match(baller->addr, baller->ai_family) ? + USETIME(timeoutms) : timeoutms; + baller_initiate(cf, data, baller); + if(!baller->result) + break; + baller_next_addr(baller); + } + if(!baller->addr) { + baller->is_done = TRUE; + } + return baller->result; +} + + +/* Used within the multi interface. Try next IP address, returns error if no + more address exists or error */ +static CURLcode baller_start_next(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct eyeballer *baller, + timediff_t timeoutms) +{ + if(cf->sockindex == FIRSTSOCKET) { + baller_next_addr(baller); + /* If we get inconclusive answers from the server(s), we make + * a second iteration over the address list */ + if(!baller->addr && baller->inconclusive && !baller->rewinded) + baller_rewind(baller); + baller_start(cf, data, baller, timeoutms); + } + else { + baller->error = 0; + baller->connected = FALSE; + baller->has_started = TRUE; + baller->is_done = TRUE; + baller->result = CURLE_COULDNT_CONNECT; + } + return baller->result; +} + +static CURLcode baller_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct eyeballer *baller, + struct curltime *now, + bool *connected) +{ + (void)cf; + *connected = baller->connected; + if(!baller->result && !*connected) { + /* evaluate again */ + baller->result = Curl_conn_cf_connect(baller->cf, data, 0, connected); + + if(!baller->result) { + if(*connected) { + baller->connected = TRUE; + baller->is_done = TRUE; + } + else if(Curl_timediff(*now, baller->started) >= baller->timeoutms) { + infof(data, "%s connect timeout after %" CURL_FORMAT_TIMEDIFF_T + "ms, move on!", baller->name, baller->timeoutms); +#if defined(ETIMEDOUT) + baller->error = ETIMEDOUT; +#endif + baller->result = CURLE_OPERATION_TIMEDOUT; + } + } + else if(baller->result == CURLE_WEIRD_SERVER_REPLY) + baller->inconclusive = TRUE; + } + return baller->result; +} + +/* + * is_connected() checks if the socket has connected. + */ +static CURLcode is_connected(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *connected) +{ + struct cf_he_ctx *ctx = cf->ctx; + struct connectdata *conn = cf->conn; + CURLcode result; + struct curltime now; + size_t i; + int ongoing, not_started; + const char *hostname; + + /* Check if any of the conn->tempsock we use for establishing connections + * succeeded and, if so, close any ongoing other ones. + * Transfer the successful conn->tempsock to conn->sock[sockindex] + * and set conn->tempsock to CURL_SOCKET_BAD. + * If transport is QUIC, we need to shutdown the ongoing 'other' + * cot ballers in a QUIC appropriate way. */ +evaluate: + *connected = FALSE; /* a very negative world view is best */ + now = Curl_now(); + ongoing = not_started = 0; + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + + if(!baller || baller->is_done) + continue; + + if(!baller->has_started) { + ++not_started; + continue; + } + baller->result = baller_connect(cf, data, baller, &now, connected); + CURL_TRC_CF(data, cf, "%s connect -> %d, connected=%d", + baller->name, baller->result, *connected); + + if(!baller->result) { + if(*connected) { + /* connected, declare the winner */ + ctx->winner = baller; + ctx->baller[i] = NULL; + break; + } + else { /* still waiting */ + ++ongoing; + } + } + else if(!baller->is_done) { + /* The baller failed to connect, start its next attempt */ + if(baller->error) { + data->state.os_errno = baller->error; + SET_SOCKERRNO(baller->error); + } + baller_start_next(cf, data, baller, Curl_timeleft(data, &now, TRUE)); + if(baller->is_done) { + CURL_TRC_CF(data, cf, "%s done", baller->name); + } + else { + /* next attempt was started */ + CURL_TRC_CF(data, cf, "%s trying next", baller->name); + ++ongoing; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + } + } + + if(ctx->winner) { + *connected = TRUE; + return CURLE_OK; + } + + /* Nothing connected, check the time before we might + * start new ballers or return ok. */ + if((ongoing || not_started) && Curl_timeleft(data, &now, TRUE) < 0) { + failf(data, "Connection timeout after %" CURL_FORMAT_CURL_OFF_T " ms", + Curl_timediff(now, data->progress.t_startsingle)); + return CURLE_OPERATION_TIMEDOUT; + } + + /* Check if we have any waiting ballers to start now. */ + if(not_started > 0) { + int added = 0; + + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + + if(!baller || baller->has_started) + continue; + /* We start its primary baller has failed to connect or if + * its start delay_ms have expired */ + if((baller->primary && baller->primary->is_done) || + Curl_timediff(now, ctx->started) >= baller->delay_ms) { + baller_start(cf, data, baller, Curl_timeleft(data, &now, TRUE)); + if(baller->is_done) { + CURL_TRC_CF(data, cf, "%s done", baller->name); + } + else { + CURL_TRC_CF(data, cf, "%s starting (timeout=%" + CURL_FORMAT_TIMEDIFF_T "ms)", + baller->name, baller->timeoutms); + ++ongoing; + ++added; + } + } + } + if(added > 0) + goto evaluate; + } + + if(ongoing > 0) { + /* We are still trying, return for more waiting */ + *connected = FALSE; + return CURLE_OK; + } + + /* all ballers have failed to connect. */ + CURL_TRC_CF(data, cf, "all eyeballers failed"); + result = CURLE_COULDNT_CONNECT; + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + if(!baller) + continue; + CURL_TRC_CF(data, cf, "%s assess started=%d, result=%d", + baller->name, baller->has_started, baller->result); + if(baller->has_started && baller->result) { + result = baller->result; + break; + } + } + +#ifndef CURL_DISABLE_PROXY + if(conn->bits.socksproxy) + hostname = conn->socks_proxy.host.name; + else if(conn->bits.httpproxy) + hostname = conn->http_proxy.host.name; + else +#endif + if(conn->bits.conn_to_host) + hostname = conn->conn_to_host.name; + else + hostname = conn->host.name; + + failf(data, "Failed to connect to %s port %u after " + "%" CURL_FORMAT_TIMEDIFF_T " ms: %s", + hostname, conn->port, + Curl_timediff(now, data->progress.t_startsingle), + curl_easy_strerror(result)); + +#ifdef WSAETIMEDOUT + if(WSAETIMEDOUT == data->state.os_errno) + result = CURLE_OPERATION_TIMEDOUT; +#elif defined(ETIMEDOUT) + if(ETIMEDOUT == data->state.os_errno) + result = CURLE_OPERATION_TIMEDOUT; +#endif + + return result; +} + +/* + * Connect to the given host with timeout, proxy or remote doesn't matter. + * There might be more than one IP address to try out. + */ +static CURLcode start_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost) +{ + struct cf_he_ctx *ctx = cf->ctx; + struct connectdata *conn = cf->conn; + CURLcode result = CURLE_COULDNT_CONNECT; + int ai_family0, ai_family1; + timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + const struct Curl_addrinfo *addr0, *addr1; + + if(timeout_ms < 0) { + /* a precaution, no need to continue if time already is up */ + failf(data, "Connection time-out"); + return CURLE_OPERATION_TIMEDOUT; + } + + ctx->started = Curl_now(); + + /* remotehost->addr is the list of addresses from the resolver, each + * with an address family. The list has at least one entry, possibly + * many more. + * We try at most 2 at a time, until we either get a connection or + * run out of addresses to try. Since likelihood of success is tied + * to the address family (e.g. IPV6 might not work at all ), we want + * the 2 connect attempt ballers to try different families, if possible. + * + */ + if(conn->ip_version == CURL_IPRESOLVE_WHATEVER) { + /* any IP version is allowed */ + ai_family0 = remotehost->addr? + remotehost->addr->ai_family : 0; +#ifdef ENABLE_IPV6 + ai_family1 = ai_family0 == AF_INET6 ? + AF_INET : AF_INET6; +#else + ai_family1 = AF_UNSPEC; +#endif + } + else { + /* only one IP version is allowed */ + ai_family0 = (conn->ip_version == CURL_IPRESOLVE_V4) ? + AF_INET : +#ifdef ENABLE_IPV6 + AF_INET6; +#else + AF_UNSPEC; +#endif + ai_family1 = AF_UNSPEC; + } + + /* Get the first address in the list that matches the family, + * this might give NULL, if we do not have any matches. */ + addr0 = addr_first_match(remotehost->addr, ai_family0); + addr1 = addr_first_match(remotehost->addr, ai_family1); + if(!addr0 && addr1) { + /* switch around, so a single baller always uses addr0 */ + addr0 = addr1; + ai_family0 = ai_family1; + addr1 = NULL; + } + + /* We found no address that matches our criteria, we cannot connect */ + if(!addr0) { + return CURLE_COULDNT_CONNECT; + } + + memset(ctx->baller, 0, sizeof(ctx->baller)); + result = eyeballer_new(&ctx->baller[0], ctx->cf_create, addr0, ai_family0, + NULL, 0, /* no primary/delay, start now */ + timeout_ms, EXPIRE_DNS_PER_NAME); + if(result) + return result; + CURL_TRC_CF(data, cf, "created %s (timeout %" + CURL_FORMAT_TIMEDIFF_T "ms)", + ctx->baller[0]->name, ctx->baller[0]->timeoutms); + if(addr1) { + /* second one gets a delayed start */ + result = eyeballer_new(&ctx->baller[1], ctx->cf_create, addr1, ai_family1, + ctx->baller[0], /* wait on that to fail */ + /* or start this delayed */ + data->set.happy_eyeballs_timeout, + timeout_ms, EXPIRE_DNS_PER_NAME2); + if(result) + return result; + CURL_TRC_CF(data, cf, "created %s (timeout %" + CURL_FORMAT_TIMEDIFF_T "ms)", + ctx->baller[1]->name, ctx->baller[1]->timeoutms); + Curl_expire(data, data->set.happy_eyeballs_timeout, + EXPIRE_HAPPY_EYEBALLS); + } + + return CURLE_OK; +} + +static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_he_ctx *ctx = cf->ctx; + size_t i; + + DEBUGASSERT(ctx); + DEBUGASSERT(data); + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + baller_free(ctx->baller[i], data); + ctx->baller[i] = NULL; + } + baller_free(ctx->winner, data); + ctx->winner = NULL; +} + +static void cf_he_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_he_ctx *ctx = cf->ctx; + size_t i; + + if(!cf->connected) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + if(!baller || !baller->cf) + continue; + Curl_conn_cf_adjust_pollset(baller->cf, data, ps); + } + CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num); + } +} + +static CURLcode cf_he_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_he_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + (void)blocking; /* TODO: do we want to support this? */ + DEBUGASSERT(ctx); + *done = FALSE; + + switch(ctx->state) { + case SCFST_INIT: + DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data)); + DEBUGASSERT(!cf->connected); + result = start_connect(cf, data, ctx->remotehost); + if(result) + return result; + ctx->state = SCFST_WAITING; + /* FALLTHROUGH */ + case SCFST_WAITING: + result = is_connected(cf, data, done); + if(!result && *done) { + DEBUGASSERT(ctx->winner); + DEBUGASSERT(ctx->winner->cf); + DEBUGASSERT(ctx->winner->cf->connected); + /* we have a winner. Install and activate it. + * close/free all others. */ + ctx->state = SCFST_DONE; + cf->connected = TRUE; + cf->next = ctx->winner->cf; + ctx->winner->cf = NULL; + cf_he_ctx_clear(cf, data); + Curl_conn_cf_cntrl(cf->next, data, TRUE, + CF_CTRL_CONN_INFO_UPDATE, 0, NULL); + + if(cf->conn->handler->protocol & PROTO_FAMILY_SSH) + Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */ + Curl_verboseconnect(data, cf->conn); + data->info.numconnects++; /* to track the # of connections made */ + } + break; + case SCFST_DONE: + *done = TRUE; + break; + } + return result; +} + +static void cf_he_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_he_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "close"); + cf_he_ctx_clear(cf, data); + cf->connected = FALSE; + ctx->state = SCFST_INIT; + + if(cf->next) { + cf->next->cft->do_close(cf->next, data); + Curl_conn_cf_discard_chain(&cf->next, data); + } +} + +static bool cf_he_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_he_ctx *ctx = cf->ctx; + size_t i; + + if(cf->connected) + return cf->next->cft->has_data_pending(cf->next, data); + + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + if(!baller || !baller->cf) + continue; + if(baller->cf->cft->has_data_pending(baller->cf, data)) + return TRUE; + } + return FALSE; +} + +static struct curltime get_max_baller_time(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query) +{ + struct cf_he_ctx *ctx = cf->ctx; + struct curltime t, tmax; + size_t i; + + memset(&tmax, 0, sizeof(tmax)); + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + + memset(&t, 0, sizeof(t)); + if(baller && baller->cf && + !baller->cf->cft->query(baller->cf, data, query, NULL, &t)) { + if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) + tmax = t; + } + } + return tmax; +} + +static CURLcode cf_he_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_he_ctx *ctx = cf->ctx; + + if(!cf->connected) { + switch(query) { + case CF_QUERY_CONNECT_REPLY_MS: { + int reply_ms = -1; + size_t i; + + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + int breply_ms; + + if(baller && baller->cf && + !baller->cf->cft->query(baller->cf, data, query, + &breply_ms, NULL)) { + if(breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms)) + reply_ms = breply_ms; + } + } + *pres1 = reply_ms; + CURL_TRC_CF(data, cf, "query connect reply: %dms", *pres1); + return CURLE_OK; + } + case CF_QUERY_TIMER_CONNECT: { + struct curltime *when = pres2; + *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT); + return CURLE_OK; + } + case CF_QUERY_TIMER_APPCONNECT: { + struct curltime *when = pres2; + *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT); + return CURLE_OK; + } + default: + break; + } + } + + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +static void cf_he_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_he_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "destroy"); + if(ctx) { + cf_he_ctx_clear(cf, data); + } + /* release any resources held in state */ + Curl_safefree(ctx); +} + +struct Curl_cftype Curl_cft_happy_eyeballs = { + "HAPPY-EYEBALLS", + 0, + CURL_LOG_LVL_NONE, + cf_he_destroy, + cf_he_connect, + cf_he_close, + Curl_cf_def_get_host, + cf_he_adjust_pollset, + cf_he_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_he_query, +}; + +/** + * Create a happy eyeball connection filter that uses the, once resolved, + * address information to connect on ip families based on connection + * configuration. + * @param pcf output, the created cfilter + * @param data easy handle used in creation + * @param conn connection the filter is created for + * @param cf_create method to create the sub-filters performing the + * actual connects. + */ +static CURLcode +cf_happy_eyeballs_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + cf_ip_connect_create *cf_create, + const struct Curl_dns_entry *remotehost, + int transport) +{ + struct cf_he_ctx *ctx = NULL; + CURLcode result; + + (void)data; + (void)conn; + *pcf = NULL; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->transport = transport; + ctx->cf_create = cf_create; + ctx->remotehost = remotehost; + + result = Curl_cf_create(pcf, &Curl_cft_happy_eyeballs, ctx); + +out: + if(result) { + Curl_safefree(*pcf); + Curl_safefree(ctx); + } + return result; +} + +struct transport_provider { + int transport; + cf_ip_connect_create *cf_create; +}; + +static +#ifndef DEBUGBUILD +const +#endif +struct transport_provider transport_providers[] = { + { TRNSPRT_TCP, Curl_cf_tcp_create }, +#ifdef ENABLE_QUIC + { TRNSPRT_QUIC, Curl_cf_quic_create }, +#endif +#ifndef CURL_DISABLE_TFTP + { TRNSPRT_UDP, Curl_cf_udp_create }, +#endif +#ifdef USE_UNIX_SOCKETS + { TRNSPRT_UNIX, Curl_cf_unix_create }, +#endif +}; + +static cf_ip_connect_create *get_cf_create(int transport) +{ + size_t i; + for(i = 0; i < ARRAYSIZE(transport_providers); ++i) { + if(transport == transport_providers[i].transport) + return transport_providers[i].cf_create; + } + return NULL; +} + +static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + int transport) +{ + cf_ip_connect_create *cf_create; + struct Curl_cfilter *cf; + CURLcode result; + + /* Need to be first */ + DEBUGASSERT(cf_at); + cf_create = get_cf_create(transport); + if(!cf_create) { + CURL_TRC_CF(data, cf_at, "unsupported transport type %d", transport); + return CURLE_UNSUPPORTED_PROTOCOL; + } + result = cf_happy_eyeballs_create(&cf, data, cf_at->conn, + cf_create, remotehost, + transport); + if(result) + return result; + + Curl_conn_cf_insert_after(cf_at, cf); + return CURLE_OK; +} + +typedef enum { + CF_SETUP_INIT, + CF_SETUP_CNNCT_EYEBALLS, + CF_SETUP_CNNCT_SOCKS, + CF_SETUP_CNNCT_HTTP_PROXY, + CF_SETUP_CNNCT_HAPROXY, + CF_SETUP_CNNCT_SSL, + CF_SETUP_DONE +} cf_setup_state; + +struct cf_setup_ctx { + cf_setup_state state; + const struct Curl_dns_entry *remotehost; + int ssl_mode; + int transport; +}; + +static CURLcode cf_setup_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_setup_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* connect current sub-chain */ +connect_sub_chain: + if(cf->next && !cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + } + + if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) { + result = cf_he_insert_after(cf, data, ctx->remotehost, ctx->transport); + if(result) + return result; + ctx->state = CF_SETUP_CNNCT_EYEBALLS; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; + } + + /* sub-chain connected, do we need to add more? */ +#ifndef CURL_DISABLE_PROXY + if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) { + result = Curl_cf_socks_proxy_insert_after(cf, data); + if(result) + return result; + ctx->state = CF_SETUP_CNNCT_SOCKS; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; + } + + if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) { +#ifdef USE_SSL + if(IS_HTTPS_PROXY(cf->conn->http_proxy.proxytype) + && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { + result = Curl_cf_ssl_proxy_insert_after(cf, data); + if(result) + return result; + } +#endif /* USE_SSL */ + +#if !defined(CURL_DISABLE_HTTP) + if(cf->conn->bits.tunnel_proxy) { + result = Curl_cf_http_proxy_insert_after(cf, data); + if(result) + return result; + } +#endif /* !CURL_DISABLE_HTTP */ + ctx->state = CF_SETUP_CNNCT_HTTP_PROXY; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; + } +#endif /* !CURL_DISABLE_PROXY */ + + if(ctx->state < CF_SETUP_CNNCT_HAPROXY) { +#if !defined(CURL_DISABLE_PROXY) + if(data->set.haproxyprotocol) { + if(Curl_conn_is_ssl(cf->conn, cf->sockindex)) { + failf(data, "haproxy protocol not support with SSL " + "encryption in place (QUIC?)"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + result = Curl_cf_haproxy_insert_after(cf, data); + if(result) + return result; + } +#endif /* !CURL_DISABLE_PROXY */ + ctx->state = CF_SETUP_CNNCT_HAPROXY; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; + } + + if(ctx->state < CF_SETUP_CNNCT_SSL) { +#ifdef USE_SSL + if((ctx->ssl_mode == CURL_CF_SSL_ENABLE + || (ctx->ssl_mode != CURL_CF_SSL_DISABLE + && cf->conn->handler->flags & PROTOPT_SSL)) /* we want SSL */ + && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */ + result = Curl_cf_ssl_insert_after(cf, data); + if(result) + return result; + } +#endif /* USE_SSL */ + ctx->state = CF_SETUP_CNNCT_SSL; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; + } + + ctx->state = CF_SETUP_DONE; + cf->connected = TRUE; + *done = TRUE; + return CURLE_OK; +} + +static void cf_setup_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_setup_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "close"); + cf->connected = FALSE; + ctx->state = CF_SETUP_INIT; + + if(cf->next) { + cf->next->cft->do_close(cf->next, data); + Curl_conn_cf_discard_chain(&cf->next, data); + } +} + +static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_setup_ctx *ctx = cf->ctx; + + (void)data; + CURL_TRC_CF(data, cf, "destroy"); + Curl_safefree(ctx); +} + + +struct Curl_cftype Curl_cft_setup = { + "SETUP", + 0, + CURL_LOG_LVL_NONE, + cf_setup_destroy, + cf_setup_connect, + cf_setup_close, + Curl_cf_def_get_host, + Curl_cf_def_adjust_pollset, + Curl_cf_def_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +static CURLcode cf_setup_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode) +{ + struct Curl_cfilter *cf = NULL; + struct cf_setup_ctx *ctx; + CURLcode result = CURLE_OK; + + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->state = CF_SETUP_INIT; + ctx->remotehost = remotehost; + ctx->ssl_mode = ssl_mode; + ctx->transport = transport; + + result = Curl_cf_create(&cf, &Curl_cft_setup, ctx); + if(result) + goto out; + ctx = NULL; + +out: + *pcf = result? NULL : cf; + free(ctx); + return result; +} + +static CURLcode cf_setup_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode); + if(result) + goto out; + Curl_conn_cf_add(data, conn, sockindex, cf); +out: + return result; +} + +#ifdef DEBUGBUILD +/* used by unit2600.c */ +void Curl_debug_set_transport_provider(int transport, + cf_ip_connect_create *cf_create) +{ + size_t i; + for(i = 0; i < ARRAYSIZE(transport_providers); ++i) { + if(transport == transport_providers[i].transport) { + transport_providers[i].cf_create = cf_create; + return; + } + } +} +#endif /* DEBUGBUILD */ + +CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode) +{ + struct Curl_cfilter *cf; + CURLcode result; + + DEBUGASSERT(data); + result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode); + if(result) + goto out; + Curl_conn_cf_insert_after(cf_at, cf); +out: + return result; +} + +CURLcode Curl_conn_setup(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + int ssl_mode) +{ + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + DEBUGASSERT(conn->handler); + +#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) + if(!conn->cfilter[sockindex] && + conn->handler->protocol == CURLPROTO_HTTPS) { + DEBUGASSERT(ssl_mode != CURL_CF_SSL_DISABLE); + result = Curl_cf_https_setup(data, conn, sockindex, remotehost); + if(result) + goto out; + } +#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */ + + /* Still no cfilter set, apply default. */ + if(!conn->cfilter[sockindex]) { + result = cf_setup_add(data, conn, sockindex, remotehost, + conn->transport, ssl_mode); + if(result) + goto out; + } + + DEBUGASSERT(conn->cfilter[sockindex]); +out: + return result; +} diff --git a/Utilities/cmcurl/lib/connect.h b/Utilities/cmcurl/lib/connect.h new file mode 100644 index 0000000..58264bd --- /dev/null +++ b/Utilities/cmcurl/lib/connect.h @@ -0,0 +1,132 @@ +#ifndef HEADER_CURL_CONNECT_H +#define HEADER_CURL_CONNECT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#include "nonblock.h" /* for curlx_nonblock(), formerly Curl_nonblock() */ +#include "sockaddr.h" +#include "timeval.h" + +struct Curl_dns_entry; + +/* generic function that returns how much time there's left to run, according + to the timeouts set */ +timediff_t Curl_timeleft(struct Curl_easy *data, + struct curltime *nowp, + bool duringconnect); + +#define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */ + +/* + * Used to extract socket and connectdata struct for the most recent + * transfer on the given Curl_easy. + * + * The returned socket will be CURL_SOCKET_BAD in case of failure! + */ +curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, + struct connectdata **connp); + +bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, + char *addr, int *port); + +void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn, + char *local_ip, int local_port); + +/* + * Curl_conncontrol() marks the end of a connection/stream. The 'closeit' + * 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. + */ + +#define CONNCTRL_KEEP 0 /* undo a marked closure */ +#define CONNCTRL_CONNECTION 1 +#define CONNCTRL_STREAM 2 + +void Curl_conncontrol(struct connectdata *conn, + int closeit +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + , const char *reason +#endif + ); + +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) +#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 /* if !DEBUGBUILD || CURL_DISABLE_VERBOSE_STRINGS */ +#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 + +/** + * Create a cfilter for making an "ip" connection to the + * given address, using parameters from `conn`. The "ip" connection + * can be a TCP socket, a UDP socket or even a QUIC connection. + * + * It MUST use only the supplied `ai` for its connection attempt. + * + * Such a filter may be used in "happy eyeball" scenarios, and its + * `connect` implementation needs to support non-blocking. Once connected, + * it MAY be installed in the connection filter chain to serve transfers. + */ +typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport); + +CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode); + +/** + * Setup the cfilters at `sockindex` in connection `conn`. + * If no filter chain is installed yet, inspects the configuration + * in `data` and `conn? to install a suitable filter chain. + */ +CURLcode Curl_conn_setup(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + int ssl_mode); + +extern struct Curl_cftype Curl_cft_happy_eyeballs; +extern struct Curl_cftype Curl_cft_setup; + +#ifdef DEBUGBUILD +void Curl_debug_set_transport_provider(int transport, + cf_ip_connect_create *cf_create); +#endif + +#endif /* HEADER_CURL_CONNECT_H */ diff --git a/Utilities/cmcurl/lib/content_encoding.c b/Utilities/cmcurl/lib/content_encoding.c new file mode 100644 index 0000000..082e0fa --- /dev/null +++ b/Utilities/cmcurl/lib/content_encoding.c @@ -0,0 +1,1034 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include <curl/curl.h> +#include <stddef.h> + +#ifdef HAVE_LIBZ +#include <cm3p/zlib.h> +#endif + +#ifdef HAVE_BROTLI +#if defined(__GNUC__) +/* Ignore -Wvla warnings in brotli headers */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wvla" +#endif +#include <brotli/decode.h> +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#endif + +#ifdef HAVE_ZSTD +#include <zstd.h> +#endif + +#include "sendf.h" +#include "http.h" +#include "content_encoding.h" +#include "strdup.h" +#include "strcase.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define CONTENT_ENCODING_DEFAULT "identity" + +#ifndef CURL_DISABLE_HTTP + +/* allow no more than 5 "chained" compression steps */ +#define MAX_ENCODE_STACK 5 + +#define DSIZ CURL_MAX_WRITE_SIZE /* buffer size for decompressed data */ + + +#ifdef HAVE_LIBZ + +/* Comment this out if zlib is always going to be at least ver. 1.2.0.4 + (doing so will reduce code size slightly). */ +#define OLD_ZLIB_SUPPORT 1 + +#define GZIP_MAGIC_0 0x1f +#define GZIP_MAGIC_1 0x8b + +/* gzip flag byte */ +#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ +#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ +#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ +#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define COMMENT 0x10 /* bit 4 set: file comment present */ +#define RESERVED 0xE0 /* bits 5..7: reserved */ + +typedef enum { + ZLIB_UNINIT, /* uninitialized */ + ZLIB_INIT, /* initialized */ + ZLIB_INFLATING, /* inflating started. */ + ZLIB_EXTERNAL_TRAILER, /* reading external trailer */ + ZLIB_GZIP_HEADER, /* reading gzip header */ + ZLIB_GZIP_INFLATING, /* inflating gzip stream */ + ZLIB_INIT_GZIP /* initialized in transparent gzip mode */ +} zlibInitState; + +/* Deflate and gzip writer. */ +struct zlib_writer { + struct Curl_cwriter super; + zlibInitState zlib_init; /* zlib init state */ + uInt trailerlen; /* Remaining trailer byte count. */ + z_stream z; /* State structure for zlib. */ +}; + + +static voidpf +zalloc_cb(voidpf opaque, unsigned int items, unsigned int size) +{ + (void) opaque; + /* not a typo, keep it calloc() */ + return (voidpf) calloc(items, size); +} + +static void +zfree_cb(voidpf opaque, voidpf ptr) +{ + (void) opaque; + free(ptr); +} + +static CURLcode +process_zlib_error(struct Curl_easy *data, z_stream *z) +{ + if(z->msg) + failf(data, "Error while processing content unencoding: %s", + z->msg); + else + failf(data, "Error while processing content unencoding: " + "Unknown failure within decompression software."); + + return CURLE_BAD_CONTENT_ENCODING; +} + +static CURLcode +exit_zlib(struct Curl_easy *data, + z_stream *z, zlibInitState *zlib_init, CURLcode result) +{ + if(*zlib_init == ZLIB_GZIP_HEADER) + Curl_safefree(z->next_in); + + if(*zlib_init != ZLIB_UNINIT) { + if(inflateEnd(z) != Z_OK && result == CURLE_OK) + result = process_zlib_error(data, z); + *zlib_init = ZLIB_UNINIT; + } + + return result; +} + +static CURLcode process_trailer(struct Curl_easy *data, + struct zlib_writer *zp) +{ + z_stream *z = &zp->z; + CURLcode result = CURLE_OK; + uInt len = z->avail_in < zp->trailerlen? z->avail_in: zp->trailerlen; + + /* Consume expected trailer bytes. Terminate stream if exhausted. + Issue an error if unexpected bytes follow. */ + + zp->trailerlen -= len; + z->avail_in -= len; + z->next_in += len; + if(z->avail_in) + result = CURLE_WRITE_ERROR; + if(result || !zp->trailerlen) + result = exit_zlib(data, z, &zp->zlib_init, result); + else { + /* Only occurs for gzip with zlib < 1.2.0.4 or raw deflate. */ + zp->zlib_init = ZLIB_EXTERNAL_TRAILER; + } + return result; +} + +static CURLcode inflate_stream(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + zlibInitState started) +{ + struct zlib_writer *zp = (struct zlib_writer *) writer; + z_stream *z = &zp->z; /* zlib state structure */ + uInt nread = z->avail_in; + Bytef *orig_in = z->next_in; + bool done = FALSE; + CURLcode result = CURLE_OK; /* Curl_client_write status */ + char *decomp; /* Put the decompressed data here. */ + + /* Check state. */ + if(zp->zlib_init != ZLIB_INIT && + zp->zlib_init != ZLIB_INFLATING && + zp->zlib_init != ZLIB_INIT_GZIP && + zp->zlib_init != ZLIB_GZIP_INFLATING) + return exit_zlib(data, z, &zp->zlib_init, CURLE_WRITE_ERROR); + + /* Dynamically allocate a buffer for decompression because it's uncommonly + large to hold on the stack */ + decomp = malloc(DSIZ); + if(!decomp) + return exit_zlib(data, z, &zp->zlib_init, CURLE_OUT_OF_MEMORY); + + /* because the buffer size is fixed, iteratively decompress and transfer to + the client via next_write function. */ + while(!done) { + int status; /* zlib status */ + done = TRUE; + + /* (re)set buffer for decompressed output for every iteration */ + z->next_out = (Bytef *) decomp; + z->avail_out = DSIZ; + +#ifdef Z_BLOCK + /* Z_BLOCK is only available in zlib ver. >= 1.2.0.5 */ + status = inflate(z, Z_BLOCK); +#else + /* fallback for zlib ver. < 1.2.0.5 */ + status = inflate(z, Z_SYNC_FLUSH); +#endif + + /* Flush output data if some. */ + if(z->avail_out != DSIZ) { + if(status == Z_OK || status == Z_STREAM_END) { + zp->zlib_init = started; /* Data started. */ + result = Curl_cwriter_write(data, writer->next, type, decomp, + DSIZ - z->avail_out); + if(result) { + exit_zlib(data, z, &zp->zlib_init, result); + break; + } + } + } + + /* Dispatch by inflate() status. */ + switch(status) { + case Z_OK: + /* Always loop: there may be unflushed latched data in zlib state. */ + done = FALSE; + break; + case Z_BUF_ERROR: + /* No more data to flush: just exit loop. */ + break; + case Z_STREAM_END: + result = process_trailer(data, zp); + break; + case Z_DATA_ERROR: + /* some servers seem to not generate zlib headers, so this is an attempt + to fix and continue anyway */ + if(zp->zlib_init == ZLIB_INIT) { + /* Do not use inflateReset2(): only available since zlib 1.2.3.4. */ + (void) inflateEnd(z); /* don't care about the return code */ + if(inflateInit2(z, -MAX_WBITS) == Z_OK) { + z->next_in = orig_in; + z->avail_in = nread; + zp->zlib_init = ZLIB_INFLATING; + zp->trailerlen = 4; /* Tolerate up to 4 unknown trailer bytes. */ + done = FALSE; + break; + } + zp->zlib_init = ZLIB_UNINIT; /* inflateEnd() already called. */ + } + result = exit_zlib(data, z, &zp->zlib_init, process_zlib_error(data, z)); + break; + default: + result = exit_zlib(data, z, &zp->zlib_init, process_zlib_error(data, z)); + break; + } + } + free(decomp); + + /* We're about to leave this call so the `nread' data bytes won't be seen + again. If we are in a state that would wrongly allow restart in raw mode + at the next call, assume output has already started. */ + if(nread && zp->zlib_init == ZLIB_INIT) + zp->zlib_init = started; /* Cannot restart anymore. */ + + return result; +} + + +/* Deflate handler. */ +static CURLcode deflate_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct zlib_writer *zp = (struct zlib_writer *) writer; + z_stream *z = &zp->z; /* zlib state structure */ + + /* Initialize zlib */ + z->zalloc = (alloc_func) zalloc_cb; + z->zfree = (free_func) zfree_cb; + + if(inflateInit(z) != Z_OK) + return process_zlib_error(data, z); + zp->zlib_init = ZLIB_INIT; + return CURLE_OK; +} + +static CURLcode deflate_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + struct zlib_writer *zp = (struct zlib_writer *) writer; + z_stream *z = &zp->z; /* zlib state structure */ + + if(!(type & CLIENTWRITE_BODY)) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + + /* Set the compressed input when this function is called */ + z->next_in = (Bytef *) buf; + z->avail_in = (uInt) nbytes; + + if(zp->zlib_init == ZLIB_EXTERNAL_TRAILER) + return process_trailer(data, zp); + + /* Now uncompress the data */ + return inflate_stream(data, writer, type, ZLIB_INFLATING); +} + +static void deflate_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct zlib_writer *zp = (struct zlib_writer *) writer; + z_stream *z = &zp->z; /* zlib state structure */ + + exit_zlib(data, z, &zp->zlib_init, CURLE_OK); +} + +static const struct Curl_cwtype deflate_encoding = { + "deflate", + NULL, + deflate_do_init, + deflate_do_write, + deflate_do_close, + sizeof(struct zlib_writer) +}; + + +/* Gzip handler. */ +static CURLcode gzip_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct zlib_writer *zp = (struct zlib_writer *) writer; + z_stream *z = &zp->z; /* zlib state structure */ + + /* Initialize zlib */ + z->zalloc = (alloc_func) zalloc_cb; + z->zfree = (free_func) zfree_cb; + + if(strcmp(zlibVersion(), "1.2.0.4") >= 0) { + /* zlib ver. >= 1.2.0.4 supports transparent gzip decompressing */ + if(inflateInit2(z, MAX_WBITS + 32) != Z_OK) { + return process_zlib_error(data, z); + } + zp->zlib_init = ZLIB_INIT_GZIP; /* Transparent gzip decompress state */ + } + else { + /* we must parse the gzip header and trailer ourselves */ + if(inflateInit2(z, -MAX_WBITS) != Z_OK) { + return process_zlib_error(data, z); + } + zp->trailerlen = 8; /* A CRC-32 and a 32-bit input size (RFC 1952, 2.2) */ + zp->zlib_init = ZLIB_INIT; /* Initial call state */ + } + + return CURLE_OK; +} + +#ifdef OLD_ZLIB_SUPPORT +/* Skip over the gzip header */ +static enum { + GZIP_OK, + GZIP_BAD, + GZIP_UNDERFLOW +} check_gzip_header(unsigned char const *data, ssize_t len, ssize_t *headerlen) +{ + int method, flags; + const ssize_t totallen = len; + + /* The shortest header is 10 bytes */ + if(len < 10) + return GZIP_UNDERFLOW; + + if((data[0] != GZIP_MAGIC_0) || (data[1] != GZIP_MAGIC_1)) + return GZIP_BAD; + + method = data[2]; + flags = data[3]; + + if(method != Z_DEFLATED || (flags & RESERVED) != 0) { + /* Can't handle this compression method or unknown flag */ + return GZIP_BAD; + } + + /* Skip over time, xflags, OS code and all previous bytes */ + len -= 10; + data += 10; + + if(flags & EXTRA_FIELD) { + ssize_t extra_len; + + if(len < 2) + return GZIP_UNDERFLOW; + + extra_len = (data[1] << 8) | data[0]; + + if(len < (extra_len + 2)) + return GZIP_UNDERFLOW; + + len -= (extra_len + 2); + data += (extra_len + 2); + } + + if(flags & ORIG_NAME) { + /* Skip over NUL-terminated file name */ + while(len && *data) { + --len; + ++data; + } + if(!len || *data) + return GZIP_UNDERFLOW; + + /* Skip over the NUL */ + --len; + ++data; + } + + if(flags & COMMENT) { + /* Skip over NUL-terminated comment */ + while(len && *data) { + --len; + ++data; + } + if(!len || *data) + return GZIP_UNDERFLOW; + + /* Skip over the NUL */ + --len; + } + + if(flags & HEAD_CRC) { + if(len < 2) + return GZIP_UNDERFLOW; + + len -= 2; + } + + *headerlen = totallen - len; + return GZIP_OK; +} +#endif + +static CURLcode gzip_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + struct zlib_writer *zp = (struct zlib_writer *) writer; + z_stream *z = &zp->z; /* zlib state structure */ + + if(!(type & CLIENTWRITE_BODY)) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + + if(zp->zlib_init == ZLIB_INIT_GZIP) { + /* Let zlib handle the gzip decompression entirely */ + z->next_in = (Bytef *) buf; + z->avail_in = (uInt) nbytes; + /* Now uncompress the data */ + return inflate_stream(data, writer, type, ZLIB_INIT_GZIP); + } + +#ifndef OLD_ZLIB_SUPPORT + /* Support for old zlib versions is compiled away and we are running with + an old version, so return an error. */ + return exit_zlib(data, z, &zp->zlib_init, CURLE_WRITE_ERROR); + +#else + /* This next mess is to get around the potential case where there isn't + * enough data passed in to skip over the gzip header. If that happens, we + * malloc a block and copy what we have then wait for the next call. If + * there still isn't enough (this is definitely a worst-case scenario), we + * make the block bigger, copy the next part in and keep waiting. + * + * This is only required with zlib versions < 1.2.0.4 as newer versions + * can handle the gzip header themselves. + */ + + switch(zp->zlib_init) { + /* Skip over gzip header? */ + case ZLIB_INIT: + { + /* Initial call state */ + ssize_t hlen; + + switch(check_gzip_header((unsigned char *) buf, nbytes, &hlen)) { + case GZIP_OK: + z->next_in = (Bytef *) buf + hlen; + z->avail_in = (uInt) (nbytes - hlen); + zp->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ + break; + + case GZIP_UNDERFLOW: + /* We need more data so we can find the end of the gzip header. It's + * possible that the memory block we malloc here will never be freed if + * the transfer abruptly aborts after this point. Since it's unlikely + * that circumstances will be right for this code path to be followed in + * the first place, and it's even more unlikely for a transfer to fail + * immediately afterwards, it should seldom be a problem. + */ + z->avail_in = (uInt) nbytes; + z->next_in = malloc(z->avail_in); + if(!z->next_in) { + return exit_zlib(data, z, &zp->zlib_init, CURLE_OUT_OF_MEMORY); + } + memcpy(z->next_in, buf, z->avail_in); + zp->zlib_init = ZLIB_GZIP_HEADER; /* Need more gzip header data state */ + /* We don't have any data to inflate yet */ + return CURLE_OK; + + case GZIP_BAD: + default: + return exit_zlib(data, z, &zp->zlib_init, process_zlib_error(data, z)); + } + + } + break; + + case ZLIB_GZIP_HEADER: + { + /* Need more gzip header data state */ + ssize_t hlen; + z->avail_in += (uInt) nbytes; + z->next_in = Curl_saferealloc(z->next_in, z->avail_in); + if(!z->next_in) { + return exit_zlib(data, z, &zp->zlib_init, CURLE_OUT_OF_MEMORY); + } + /* Append the new block of data to the previous one */ + memcpy(z->next_in + z->avail_in - nbytes, buf, nbytes); + + switch(check_gzip_header(z->next_in, z->avail_in, &hlen)) { + case GZIP_OK: + /* This is the zlib stream data */ + free(z->next_in); + /* Don't point into the malloced block since we just freed it */ + z->next_in = (Bytef *) buf + hlen + nbytes - z->avail_in; + z->avail_in = (uInt) (z->avail_in - hlen); + zp->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ + break; + + case GZIP_UNDERFLOW: + /* We still don't have any data to inflate! */ + return CURLE_OK; + + case GZIP_BAD: + default: + return exit_zlib(data, z, &zp->zlib_init, process_zlib_error(data, z)); + } + + } + break; + + case ZLIB_EXTERNAL_TRAILER: + z->next_in = (Bytef *) buf; + z->avail_in = (uInt) nbytes; + return process_trailer(data, zp); + + case ZLIB_GZIP_INFLATING: + default: + /* Inflating stream state */ + z->next_in = (Bytef *) buf; + z->avail_in = (uInt) nbytes; + break; + } + + if(z->avail_in == 0) { + /* We don't have any data to inflate; wait until next time */ + return CURLE_OK; + } + + /* We've parsed the header, now uncompress the data */ + return inflate_stream(data, writer, type, ZLIB_GZIP_INFLATING); +#endif +} + +static void gzip_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct zlib_writer *zp = (struct zlib_writer *) writer; + z_stream *z = &zp->z; /* zlib state structure */ + + exit_zlib(data, z, &zp->zlib_init, CURLE_OK); +} + +static const struct Curl_cwtype gzip_encoding = { + "gzip", + "x-gzip", + gzip_do_init, + gzip_do_write, + gzip_do_close, + sizeof(struct zlib_writer) +}; + +#endif /* HAVE_LIBZ */ + + +#ifdef HAVE_BROTLI +/* Brotli writer. */ +struct brotli_writer { + struct Curl_cwriter super; + BrotliDecoderState *br; /* State structure for brotli. */ +}; + +static CURLcode brotli_map_error(BrotliDecoderErrorCode be) +{ + switch(be) { + case BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE: + case BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE: + case BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET: + case BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME: + case BROTLI_DECODER_ERROR_FORMAT_CL_SPACE: + case BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE: + case BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT: + case BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1: + case BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2: + case BROTLI_DECODER_ERROR_FORMAT_TRANSFORM: + case BROTLI_DECODER_ERROR_FORMAT_DICTIONARY: + case BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS: + case BROTLI_DECODER_ERROR_FORMAT_PADDING_1: + case BROTLI_DECODER_ERROR_FORMAT_PADDING_2: +#ifdef BROTLI_DECODER_ERROR_COMPOUND_DICTIONARY + case BROTLI_DECODER_ERROR_COMPOUND_DICTIONARY: +#endif +#ifdef BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET + case BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET: +#endif + case BROTLI_DECODER_ERROR_INVALID_ARGUMENTS: + return CURLE_BAD_CONTENT_ENCODING; + case BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES: + case BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS: + case BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP: + case BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1: + case BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2: + case BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES: + return CURLE_OUT_OF_MEMORY; + default: + break; + } + return CURLE_WRITE_ERROR; +} + +static CURLcode brotli_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct brotli_writer *bp = (struct brotli_writer *) writer; + (void) data; + + bp->br = BrotliDecoderCreateInstance(NULL, NULL, NULL); + return bp->br? CURLE_OK: CURLE_OUT_OF_MEMORY; +} + +static CURLcode brotli_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + struct brotli_writer *bp = (struct brotli_writer *) writer; + const uint8_t *src = (const uint8_t *) buf; + char *decomp; + uint8_t *dst; + size_t dstleft; + CURLcode result = CURLE_OK; + BrotliDecoderResult r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + if(!(type & CLIENTWRITE_BODY)) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + + if(!bp->br) + return CURLE_WRITE_ERROR; /* Stream already ended. */ + + decomp = malloc(DSIZ); + if(!decomp) + return CURLE_OUT_OF_MEMORY; + + while((nbytes || r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) && + result == CURLE_OK) { + dst = (uint8_t *) decomp; + dstleft = DSIZ; + r = BrotliDecoderDecompressStream(bp->br, + &nbytes, &src, &dstleft, &dst, NULL); + result = Curl_cwriter_write(data, writer->next, type, + decomp, DSIZ - dstleft); + if(result) + break; + switch(r) { + case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: + case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: + break; + case BROTLI_DECODER_RESULT_SUCCESS: + BrotliDecoderDestroyInstance(bp->br); + bp->br = NULL; + if(nbytes) + result = CURLE_WRITE_ERROR; + break; + default: + result = brotli_map_error(BrotliDecoderGetErrorCode(bp->br)); + break; + } + } + free(decomp); + return result; +} + +static void brotli_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct brotli_writer *bp = (struct brotli_writer *) writer; + + (void) data; + + if(bp->br) { + BrotliDecoderDestroyInstance(bp->br); + bp->br = NULL; + } +} + +static const struct Curl_cwtype brotli_encoding = { + "br", + NULL, + brotli_do_init, + brotli_do_write, + brotli_do_close, + sizeof(struct brotli_writer) +}; +#endif + + +#ifdef HAVE_ZSTD +/* Zstd writer. */ +struct zstd_writer { + struct Curl_cwriter super; + ZSTD_DStream *zds; /* State structure for zstd. */ + void *decomp; +}; + +static CURLcode zstd_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct zstd_writer *zp = (struct zstd_writer *) writer; + + (void)data; + + zp->zds = ZSTD_createDStream(); + zp->decomp = NULL; + return zp->zds ? CURLE_OK : CURLE_OUT_OF_MEMORY; +} + +static CURLcode zstd_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + CURLcode result = CURLE_OK; + struct zstd_writer *zp = (struct zstd_writer *) writer; + ZSTD_inBuffer in; + ZSTD_outBuffer out; + size_t errorCode; + + if(!(type & CLIENTWRITE_BODY)) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + + if(!zp->decomp) { + zp->decomp = malloc(DSIZ); + if(!zp->decomp) + return CURLE_OUT_OF_MEMORY; + } + in.pos = 0; + in.src = buf; + in.size = nbytes; + + for(;;) { + out.pos = 0; + out.dst = zp->decomp; + out.size = DSIZ; + + errorCode = ZSTD_decompressStream(zp->zds, &out, &in); + if(ZSTD_isError(errorCode)) { + return CURLE_BAD_CONTENT_ENCODING; + } + if(out.pos > 0) { + result = Curl_cwriter_write(data, writer->next, type, + zp->decomp, out.pos); + if(result) + break; + } + if((in.pos == nbytes) && (out.pos < out.size)) + break; + } + + return result; +} + +static void zstd_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct zstd_writer *zp = (struct zstd_writer *) writer; + + (void)data; + + if(zp->decomp) { + free(zp->decomp); + zp->decomp = NULL; + } + if(zp->zds) { + ZSTD_freeDStream(zp->zds); + zp->zds = NULL; + } +} + +static const struct Curl_cwtype zstd_encoding = { + "zstd", + NULL, + zstd_do_init, + zstd_do_write, + zstd_do_close, + sizeof(struct zstd_writer) +}; +#endif + + +/* Identity handler. */ +static const struct Curl_cwtype identity_encoding = { + "identity", + "none", + Curl_cwriter_def_init, + Curl_cwriter_def_write, + Curl_cwriter_def_close, + sizeof(struct Curl_cwriter) +}; + + +/* supported content encodings table. */ +static const struct Curl_cwtype * const encodings[] = { + &identity_encoding, +#ifdef HAVE_LIBZ + &deflate_encoding, + &gzip_encoding, +#endif +#ifdef HAVE_BROTLI + &brotli_encoding, +#endif +#ifdef HAVE_ZSTD + &zstd_encoding, +#endif + NULL +}; + + +/* Provide a list of comma-separated names of supported encodings. +*/ +void Curl_all_content_encodings(char *buf, size_t blen) +{ + size_t len = 0; + const struct Curl_cwtype * const *cep; + const struct Curl_cwtype *ce; + + DEBUGASSERT(buf); + DEBUGASSERT(blen); + buf[0] = 0; + + for(cep = encodings; *cep; cep++) { + ce = *cep; + if(!strcasecompare(ce->name, CONTENT_ENCODING_DEFAULT)) + len += strlen(ce->name) + 2; + } + + if(!len) { + if(blen >= sizeof(CONTENT_ENCODING_DEFAULT)) + strcpy(buf, CONTENT_ENCODING_DEFAULT); + } + else if(blen > len) { + char *p = buf; + for(cep = encodings; *cep; cep++) { + ce = *cep; + if(!strcasecompare(ce->name, CONTENT_ENCODING_DEFAULT)) { + strcpy(p, ce->name); + p += strlen(p); + *p++ = ','; + *p++ = ' '; + } + } + p[-2] = '\0'; + } +} + +/* Deferred error dummy writer. */ +static CURLcode error_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + (void)data; + (void)writer; + return CURLE_OK; +} + +static CURLcode error_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + char all[256]; + (void)Curl_all_content_encodings(all, sizeof(all)); + + (void) writer; + (void) buf; + (void) nbytes; + + if(!(type & CLIENTWRITE_BODY)) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + + failf(data, "Unrecognized content encoding type. " + "libcurl understands %s content encodings.", all); + return CURLE_BAD_CONTENT_ENCODING; +} + +static void error_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + (void) data; + (void) writer; +} + +static const struct Curl_cwtype error_writer = { + "ce-error", + NULL, + error_do_init, + error_do_write, + error_do_close, + sizeof(struct Curl_cwriter) +}; + +/* Find the content encoding by name. */ +static const struct Curl_cwtype *find_encoding(const char *name, + size_t len) +{ + const struct Curl_cwtype * const *cep; + + for(cep = encodings; *cep; cep++) { + const struct Curl_cwtype *ce = *cep; + if((strncasecompare(name, ce->name, len) && !ce->name[len]) || + (ce->alias && strncasecompare(name, ce->alias, len) && !ce->alias[len])) + return ce; + } + return NULL; +} + +/* Set-up the unencoding stack from the Content-Encoding header value. + * See RFC 7231 section 3.1.2.2. */ +CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, + const char *enclist, int is_transfer) +{ + struct SingleRequest *k = &data->req; + Curl_cwriter_phase phase = is_transfer? + CURL_CW_TRANSFER_DECODE:CURL_CW_CONTENT_DECODE; + CURLcode result; + + do { + const char *name; + size_t namelen; + + /* Parse a single encoding name. */ + while(ISBLANK(*enclist) || *enclist == ',') + enclist++; + + name = enclist; + + for(namelen = 0; *enclist && *enclist != ','; enclist++) + if(!ISSPACE(*enclist)) + namelen = enclist - name + 1; + + /* Special case: chunked encoding is handled at the reader level. */ + if(is_transfer && namelen == 7 && strncasecompare(name, "chunked", 7)) { + k->chunk = TRUE; /* chunks coming our way. */ + Curl_httpchunk_init(data); /* init our chunky engine. */ + } + else if(namelen) { + const struct Curl_cwtype *cwt; + struct Curl_cwriter *writer; + + if((is_transfer && !data->set.http_transfer_encoding) || + (!is_transfer && data->set.http_ce_skip)) { + /* not requested, ignore */ + return CURLE_OK; + } + + if(Curl_cwriter_count(data, phase) + 1 >= MAX_ENCODE_STACK) { + failf(data, "Reject response due to more than %u content encodings", + MAX_ENCODE_STACK); + return CURLE_BAD_CONTENT_ENCODING; + } + + cwt = find_encoding(name, namelen); + if(!cwt) + cwt = &error_writer; /* Defer error at use. */ + + result = Curl_cwriter_create(&writer, data, cwt, phase); + if(result) + return result; + + result = Curl_cwriter_add(data, writer); + if(result) { + Curl_cwriter_free(data, writer); + return result; + } + } + } while(*enclist); + + return CURLE_OK; +} + +#else +/* Stubs for builds without HTTP. */ +CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, + const char *enclist, int is_transfer) +{ + (void) data; + (void) enclist; + (void) is_transfer; + return CURLE_NOT_BUILT_IN; +} + +void Curl_all_content_encodings(char *buf, size_t blen) +{ + DEBUGASSERT(buf); + DEBUGASSERT(blen); + if(blen < sizeof(CONTENT_ENCODING_DEFAULT)) + buf[0] = 0; + else + strcpy(buf, CONTENT_ENCODING_DEFAULT); +} + + +#endif /* CURL_DISABLE_HTTP */ diff --git a/Utilities/cmcurl/lib/content_encoding.h b/Utilities/cmcurl/lib/content_encoding.h new file mode 100644 index 0000000..1addf23 --- /dev/null +++ b/Utilities/cmcurl/lib/content_encoding.h @@ -0,0 +1,34 @@ +#ifndef HEADER_CURL_CONTENT_ENCODING_H +#define HEADER_CURL_CONTENT_ENCODING_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +struct Curl_cwriter; + +void Curl_all_content_encodings(char *buf, size_t blen); + +CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, + const char *enclist, int is_transfer); +#endif /* HEADER_CURL_CONTENT_ENCODING_H */ diff --git a/Utilities/cmcurl/lib/cookie.c b/Utilities/cmcurl/lib/cookie.c new file mode 100644 index 0000000..9095cea --- /dev/null +++ b/Utilities/cmcurl/lib/cookie.c @@ -0,0 +1,1785 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/*** + + +RECEIVING COOKIE INFORMATION +============================ + +struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, + const char *file, struct CookieInfo *inc, bool newsession); + + Inits a cookie struct to store data in a local file. This is always + called before any cookies are set. + +struct Cookie *Curl_cookie_add(struct Curl_easy *data, + struct CookieInfo *c, bool httpheader, bool noexpire, + char *lineptr, const char *domain, const char *path, + bool secure); + + The 'lineptr' parameter is a full "Set-cookie:" line as + received from a server. + + The function need to replace previously stored lines that this new + line supersedes. + + It may remove lines that are expired. + + It should return an indication of success/error. + + +SENDING COOKIE INFORMATION +========================== + +struct Cookies *Curl_cookie_getlist(struct CookieInfo *cookie, + char *host, char *path, bool secure); + + For a given host and path, return a linked list of cookies that + the client should send to the server if used now. The secure + boolean informs the cookie if a secure connection is achieved or + not. + + It shall only return cookies that haven't expired. + + +Example set of cookies: + + Set-cookie: PRODUCTINFO=webxpress; domain=.fidelity.com; path=/; secure + Set-cookie: PERSONALIZE=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; + domain=.fidelity.com; path=/ftgw; secure + Set-cookie: FidHist=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; + domain=.fidelity.com; path=/; secure + Set-cookie: FidOrder=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; + domain=.fidelity.com; path=/; secure + Set-cookie: DisPend=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; + domain=.fidelity.com; path=/; secure + Set-cookie: FidDis=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; + domain=.fidelity.com; path=/; secure + Set-cookie: + Session_Key@6791a9e0-901a-11d0-a1c8-9b012c88aa77=none;expires=Monday, + 13-Jun-1988 03:04:55 GMT; domain=.fidelity.com; path=/; secure +****/ + + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + +#include "urldata.h" +#include "cookie.h" +#include "psl.h" +#include "strtok.h" +#include "sendf.h" +#include "slist.h" +#include "share.h" +#include "strtoofft.h" +#include "strcase.h" +#include "curl_get_line.h" +#include "curl_memrchr.h" +#include "parsedate.h" +#include "rename.h" +#include "fopen.h" +#include "strdup.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +static void strstore(char **str, const char *newstr, size_t len); + +static void freecookie(struct Cookie *co) +{ + free(co->domain); + free(co->path); + free(co->spath); + free(co->name); + free(co->value); + free(co); +} + +static bool cookie_tailmatch(const char *cookie_domain, + size_t cookie_domain_len, + const char *hostname) +{ + size_t hostname_len = strlen(hostname); + + if(hostname_len < cookie_domain_len) + return FALSE; + + if(!strncasecompare(cookie_domain, + hostname + hostname_len-cookie_domain_len, + cookie_domain_len)) + return FALSE; + + /* + * A lead char of cookie_domain is not '.'. + * RFC6265 4.1.2.3. The Domain Attribute says: + * For example, if the value of the Domain attribute is + * "example.com", the user agent will include the cookie in the Cookie + * header when making HTTP requests to example.com, www.example.com, and + * www.corp.example.com. + */ + if(hostname_len == cookie_domain_len) + return TRUE; + if('.' == *(hostname + hostname_len - cookie_domain_len - 1)) + return TRUE; + return FALSE; +} + +/* + * matching cookie path and url path + * RFC6265 5.1.4 Paths and Path-Match + */ +static bool pathmatch(const char *cookie_path, const char *request_uri) +{ + size_t cookie_path_len; + size_t uri_path_len; + char *uri_path = NULL; + char *pos; + bool ret = FALSE; + + /* cookie_path must not have last '/' separator. ex: /sample */ + cookie_path_len = strlen(cookie_path); + if(1 == cookie_path_len) { + /* cookie_path must be '/' */ + return TRUE; + } + + uri_path = strdup(request_uri); + if(!uri_path) + return FALSE; + pos = strchr(uri_path, '?'); + if(pos) + *pos = 0x0; + + /* #-fragments are already cut off! */ + if(0 == strlen(uri_path) || uri_path[0] != '/') { + strstore(&uri_path, "/", 1); + if(!uri_path) + return FALSE; + } + + /* + * here, RFC6265 5.1.4 says + * 4. Output the characters of the uri-path from the first character up + * to, but not including, the right-most %x2F ("/"). + * but URL path /hoge?fuga=xxx means /hoge/index.cgi?fuga=xxx in some site + * without redirect. + * Ignore this algorithm because /hoge is uri path for this case + * (uri path is not /). + */ + + uri_path_len = strlen(uri_path); + + if(uri_path_len < cookie_path_len) { + ret = FALSE; + goto pathmatched; + } + + /* not using checkprefix() because matching should be case-sensitive */ + if(strncmp(cookie_path, uri_path, cookie_path_len)) { + ret = FALSE; + goto pathmatched; + } + + /* The cookie-path and the uri-path are identical. */ + if(cookie_path_len == uri_path_len) { + ret = TRUE; + goto pathmatched; + } + + /* here, cookie_path_len < uri_path_len */ + if(uri_path[cookie_path_len] == '/') { + ret = TRUE; + goto pathmatched; + } + + ret = FALSE; + +pathmatched: + free(uri_path); + return ret; +} + +/* + * Return the top-level domain, for optimal hashing. + */ +static const char *get_top_domain(const char * const domain, size_t *outlen) +{ + size_t len = 0; + const char *first = NULL, *last; + + if(domain) { + len = strlen(domain); + last = memrchr(domain, '.', len); + if(last) { + first = memrchr(domain, '.', (last - domain)); + if(first) + len -= (++first - domain); + } + } + + if(outlen) + *outlen = len; + + return first? first: domain; +} + +/* Avoid C1001, an "internal error" with MSVC14 */ +#if defined(_MSC_VER) && (_MSC_VER == 1900) +#pragma optimize("", off) +#endif + +/* + * A case-insensitive hash for the cookie domains. + */ +static size_t cookie_hash_domain(const char *domain, const size_t len) +{ + const char *end = domain + len; + size_t h = 5381; + + while(domain < end) { + h += h << 5; + h ^= Curl_raw_toupper(*domain++); + } + + return (h % COOKIE_HASH_SIZE); +} + +#if defined(_MSC_VER) && (_MSC_VER == 1900) +#pragma optimize("", on) +#endif + +/* + * Hash this domain. + */ +static size_t cookiehash(const char * const domain) +{ + const char *top; + size_t len; + + if(!domain || Curl_host_is_ipnum(domain)) + return 0; + + top = get_top_domain(domain, &len); + return cookie_hash_domain(top, len); +} + +/* + * cookie path sanitize + */ +static char *sanitize_cookie_path(const char *cookie_path) +{ + size_t len; + char *new_path = strdup(cookie_path); + if(!new_path) + return NULL; + + /* some stupid site sends path attribute with '"'. */ + len = strlen(new_path); + if(new_path[0] == '\"') { + memmove(new_path, new_path + 1, len); + len--; + } + if(len && (new_path[len - 1] == '\"')) { + new_path[--len] = 0x0; + } + + /* RFC6265 5.2.4 The Path Attribute */ + if(new_path[0] != '/') { + /* Let cookie-path be the default-path. */ + strstore(&new_path, "/", 1); + return new_path; + } + + /* convert /hoge/ to /hoge */ + if(len && new_path[len - 1] == '/') { + new_path[len - 1] = 0x0; + } + + return new_path; +} + +/* + * Load cookies from all given cookie files (CURLOPT_COOKIEFILE). + * + * NOTE: OOM or cookie parsing failures are ignored. + */ +void Curl_cookie_loadfiles(struct Curl_easy *data) +{ + struct curl_slist *list = data->state.cookielist; + if(list) { + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + while(list) { + struct CookieInfo *newcookies = + Curl_cookie_init(data, list->data, data->cookies, + data->set.cookiesession); + if(!newcookies) + /* + * Failure may be due to OOM or a bad cookie; both are ignored + * but only the first should be + */ + infof(data, "ignoring failed cookie_init for %s", list->data); + else + data->cookies = newcookies; + list = list->next; + } + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } +} + +/* + * strstore + * + * A thin wrapper around strdup which ensures that any memory allocated at + * *str will be freed before the string allocated by strdup is stored there. + * The intended usecase is repeated assignments to the same variable during + * parsing in a last-wins scenario. The caller is responsible for checking + * for OOM errors. + */ +static void strstore(char **str, const char *newstr, size_t len) +{ + DEBUGASSERT(newstr); + DEBUGASSERT(str); + free(*str); + *str = Curl_strndup(newstr, len); +} + +/* + * remove_expired + * + * Remove expired cookies from the hash by inspecting the expires timestamp on + * each cookie in the hash, freeing and deleting any where the timestamp is in + * the past. If the cookiejar has recorded the next timestamp at which one or + * more cookies expire, then processing will exit early in case this timestamp + * is in the future. + */ +static void remove_expired(struct CookieInfo *cookies) +{ + struct Cookie *co, *nx; + curl_off_t now = (curl_off_t)time(NULL); + unsigned int i; + + /* + * If the earliest expiration timestamp in the jar is in the future we can + * skip scanning the whole jar and instead exit early as there won't be any + * cookies to evict. If we need to evict however, reset the next_expiration + * counter in order to track the next one. In case the recorded first + * expiration is the max offset, then perform the safe fallback of checking + * all cookies. + */ + if(now < cookies->next_expiration && + cookies->next_expiration != CURL_OFF_T_MAX) + return; + else + cookies->next_expiration = CURL_OFF_T_MAX; + + for(i = 0; i < COOKIE_HASH_SIZE; i++) { + struct Cookie *pv = NULL; + co = cookies->cookies[i]; + while(co) { + nx = co->next; + if(co->expires && co->expires < now) { + if(!pv) { + cookies->cookies[i] = co->next; + } + else { + pv->next = co->next; + } + cookies->numcookies--; + freecookie(co); + } + else { + /* + * If this cookie has an expiration timestamp earlier than what we've + * seen so far then record it for the next round of expirations. + */ + if(co->expires && co->expires < cookies->next_expiration) + cookies->next_expiration = co->expires; + pv = co; + } + co = nx; + } + } +} + +/* Make sure domain contains a dot or is localhost. */ +static bool bad_domain(const char *domain, size_t len) +{ + if((len == 9) && strncasecompare(domain, "localhost", 9)) + return FALSE; + else { + /* there must be a dot present, but that dot must not be a trailing dot */ + char *dot = memchr(domain, '.', len); + if(dot) { + size_t i = dot - domain; + if((len - i) > 1) + /* the dot is not the last byte */ + return FALSE; + } + } + return TRUE; +} + +/* + RFC 6265 section 4.1.1 says a server should accept this range: + + cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E + + But 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 int invalid_octets(const char *p) +{ + /* Reject all bytes \x01 - \x1f (*except* \x09, TAB) + \x7f */ + static const char badoctets[] = { + "\x01\x02\x03\x04\x05\x06\x07\x08\x0a" + "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" + "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f" + }; + size_t len; + /* scan for all the octets that are *not* in cookie-octet */ + len = strcspn(p, badoctets); + return (p[len] != '\0'); +} + +/* + * Curl_cookie_add + * + * Add a single cookie line to the cookie keeping object. Be aware that + * sometimes we get an IP-only host name, and that might also be a numerical + * IPv6 address. + * + * Returns NULL on out of memory or invalid cookie. This is suboptimal, + * as they should be treated separately. + */ +struct Cookie * +Curl_cookie_add(struct Curl_easy *data, + struct CookieInfo *c, + 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 */ +{ + struct Cookie *clist; + struct Cookie *co; + struct Cookie *lastc = NULL; + struct Cookie *replace_co = NULL; + struct Cookie *replace_clist = NULL; + time_t now = time(NULL); + bool replace_old = FALSE; + bool badcookie = FALSE; /* cookies are good by default. mmmmm yummy */ + size_t myhash; + + DEBUGASSERT(data); + DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */ + if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT) + return NULL; + + /* First, alloc and init a new struct for it */ + co = calloc(1, sizeof(struct Cookie)); + if(!co) + return NULL; /* bail out if we're this low on memory */ + + if(httpheader) { + /* This line was read off an HTTP-header */ + const char *ptr; + + size_t linelength = strlen(lineptr); + if(linelength > MAX_COOKIE_LINE) { + /* discard overly long lines at once */ + free(co); + return NULL; + } + + ptr = lineptr; + do { + size_t vlen; + size_t nlen; + + while(*ptr && ISBLANK(*ptr)) + ptr++; + + /* we have a <name>=<value> pair or a stand-alone word here */ + nlen = strcspn(ptr, ";\t\r\n="); + if(nlen) { + bool done = FALSE; + bool sep = FALSE; + const char *namep = ptr; + const char *valuep; + + ptr += nlen; + + /* trim trailing spaces and tabs after name */ + while(nlen && ISBLANK(namep[nlen - 1])) + nlen--; + + if(*ptr == '=') { + vlen = strcspn(++ptr, ";\r\n"); + valuep = ptr; + sep = TRUE; + ptr = &valuep[vlen]; + + /* Strip off trailing whitespace from the value */ + while(vlen && ISBLANK(valuep[vlen-1])) + vlen--; + + /* Skip leading whitespace from the value */ + while(vlen && ISBLANK(*valuep)) { + valuep++; + vlen--; + } + + /* Reject cookies with a TAB inside the value */ + if(memchr(valuep, '\t', vlen)) { + freecookie(co); + infof(data, "cookie contains TAB, dropping"); + return NULL; + } + } + else { + valuep = NULL; + vlen = 0; + } + + /* + * Check for too long individual name or contents, or too long + * combination of name + contents. Chrome and Firefox support 4095 or + * 4096 bytes combo + */ + if(nlen >= (MAX_NAME-1) || vlen >= (MAX_NAME-1) || + ((nlen + vlen) > MAX_NAME)) { + freecookie(co); + infof(data, "oversized cookie dropped, name/val %zu + %zu bytes", + nlen, vlen); + return NULL; + } + + /* + * Check if we have a reserved prefix set before anything else, as we + * otherwise have to test for the prefix in both the cookie name and + * "the rest". Prefixes must start with '__' and end with a '-', so + * only test for names where that can possibly be true. + */ + if(nlen >= 7 && namep[0] == '_' && namep[1] == '_') { + if(strncasecompare("__Secure-", namep, 9)) + co->prefix |= COOKIE_PREFIX__SECURE; + else if(strncasecompare("__Host-", namep, 7)) + co->prefix |= COOKIE_PREFIX__HOST; + } + + /* + * Use strstore() below to properly deal with received cookie + * headers that have the same string property set more than once, + * and then we use the last one. + */ + + if(!co->name) { + /* The very first name/value pair is the actual cookie name */ + if(!sep) { + /* Bad name/value pair. */ + badcookie = TRUE; + break; + } + strstore(&co->name, namep, nlen); + strstore(&co->value, valuep, vlen); + done = TRUE; + if(!co->name || !co->value) { + badcookie = TRUE; + break; + } + if(invalid_octets(co->value) || invalid_octets(co->name)) { + infof(data, "invalid octets in name/value, cookie dropped"); + badcookie = TRUE; + break; + } + } + else if(!vlen) { + /* + * this was a "<name>=" with no content, and we must allow + * 'secure' and 'httponly' specified this weirdly + */ + done = TRUE; + /* + * secure cookies are only allowed to be set when the connection is + * using a secure protocol, or when the cookie is being set by + * reading from file + */ + if((nlen == 6) && strncasecompare("secure", namep, 6)) { + if(secure || !c->running) { + co->secure = TRUE; + } + else { + badcookie = TRUE; + break; + } + } + else if((nlen == 8) && strncasecompare("httponly", namep, 8)) + co->httponly = TRUE; + else if(sep) + /* there was a '=' so we're not done parsing this field */ + done = FALSE; + } + if(done) + ; + else if((nlen == 4) && strncasecompare("path", namep, 4)) { + strstore(&co->path, valuep, vlen); + if(!co->path) { + badcookie = TRUE; /* out of memory bad */ + break; + } + free(co->spath); /* if this is set again */ + co->spath = sanitize_cookie_path(co->path); + if(!co->spath) { + badcookie = TRUE; /* out of memory bad */ + break; + } + } + else if((nlen == 6) && + strncasecompare("domain", namep, 6) && vlen) { + bool is_ip; + + /* + * Now, we make sure that our host is within the given domain, or + * the given domain is not valid and thus cannot be set. + */ + + if('.' == valuep[0]) { + valuep++; /* ignore preceding dot */ + vlen--; + } + +#ifndef USE_LIBPSL + /* + * Without PSL we don't know when the incoming cookie is set on a + * TLD or otherwise "protected" suffix. To reduce risk, we require a + * dot OR the exact host name being "localhost". + */ + if(bad_domain(valuep, vlen)) + domain = ":"; +#endif + + is_ip = Curl_host_is_ipnum(domain ? domain : valuep); + + if(!domain + || (is_ip && !strncmp(valuep, domain, vlen) && + (vlen == strlen(domain))) + || (!is_ip && cookie_tailmatch(valuep, vlen, domain))) { + strstore(&co->domain, valuep, vlen); + if(!co->domain) { + badcookie = TRUE; + break; + } + if(!is_ip) + co->tailmatch = TRUE; /* we always do that if the domain name was + given */ + } + else { + /* + * We did not get a tailmatch and then the attempted set domain is + * not a domain to which the current host belongs. Mark as bad. + */ + badcookie = TRUE; + infof(data, "skipped cookie with bad tailmatch domain: %s", + valuep); + } + } + else if((nlen == 7) && strncasecompare("version", namep, 7)) { + /* just ignore */ + } + else if((nlen == 7) && strncasecompare("max-age", namep, 7)) { + /* + * Defined in RFC2109: + * + * Optional. The Max-Age attribute defines the lifetime of the + * cookie, in seconds. The delta-seconds value is a decimal non- + * negative integer. After delta-seconds seconds elapse, the + * client should discard the cookie. A value of zero means the + * cookie should be discarded immediately. + */ + CURLofft offt; + const char *maxage = valuep; + offt = curlx_strtoofft((*maxage == '\"')? + &maxage[1]:&maxage[0], NULL, 10, + &co->expires); + switch(offt) { + case CURL_OFFT_FLOW: + /* overflow, used max value */ + co->expires = CURL_OFF_T_MAX; + break; + case CURL_OFFT_INVAL: + /* negative or otherwise bad, expire */ + co->expires = 1; + break; + case CURL_OFFT_OK: + if(!co->expires) + /* already expired */ + co->expires = 1; + else if(CURL_OFF_T_MAX - now < co->expires) + /* would overflow */ + co->expires = CURL_OFF_T_MAX; + else + co->expires += now; + break; + } + } + else if((nlen == 7) && strncasecompare("expires", namep, 7)) { + char date[128]; + if(!co->expires && (vlen < sizeof(date))) { + /* copy the date so that it can be null terminated */ + memcpy(date, valuep, vlen); + date[vlen] = 0; + /* + * Let max-age have priority. + * + * If the date cannot get parsed for whatever reason, the cookie + * will be treated as a session cookie + */ + co->expires = Curl_getdate_capped(date); + + /* + * Session cookies have expires set to 0 so if we get that back + * from the date parser let's add a second to make it a + * non-session cookie + */ + if(co->expires == 0) + co->expires = 1; + else if(co->expires < 0) + co->expires = 0; + } + } + + /* + * Else, this is the second (or more) name we don't know about! + */ + } + else { + /* this is an "illegal" <what>=<this> pair */ + } + + while(*ptr && ISBLANK(*ptr)) + ptr++; + if(*ptr == ';') + ptr++; + else + break; + } while(1); + + if(!badcookie && !co->domain) { + if(domain) { + /* no domain was given in the header line, set the default */ + co->domain = strdup(domain); + if(!co->domain) + badcookie = TRUE; + } + } + + if(!badcookie && !co->path && path) { + /* + * No path was given in the header line, set the default. Note that the + * passed-in path to this function MAY have a '?' and following part that + * MUST NOT be stored as part of the path. + */ + char *queryp = strchr(path, '?'); + + /* + * queryp is where the interesting part of the path ends, so now we + * want to the find the last + */ + char *endslash; + if(!queryp) + endslash = strrchr(path, '/'); + else + endslash = memrchr(path, '/', (queryp - path)); + if(endslash) { + size_t pathlen = (endslash-path + 1); /* include end slash */ + co->path = malloc(pathlen + 1); /* one extra for the zero byte */ + if(co->path) { + memcpy(co->path, path, pathlen); + co->path[pathlen] = 0; /* null-terminate */ + co->spath = sanitize_cookie_path(co->path); + if(!co->spath) + badcookie = TRUE; /* out of memory bad */ + } + else + badcookie = TRUE; + } + } + + /* + * If we didn't get a cookie name, or a bad one, the this is an illegal + * line so bail out. + */ + if(badcookie || !co->name) { + freecookie(co); + return NULL; + } + data->req.setcookies++; + } + else { + /* + * This line is NOT an HTTP header style line, we do offer support for + * reading the odd netscape cookies-file format here + */ + char *ptr; + char *firstptr; + char *tok_buf = NULL; + int fields; + + /* + * IE introduced HTTP-only cookies to prevent XSS attacks. Cookies marked + * with httpOnly after the domain name are not accessible from javascripts, + * but since curl does not operate at javascript level, we include them + * anyway. In Firefox's cookie files, these lines are preceded with + * #HttpOnly_ and then everything is as usual, so we skip 10 characters of + * the line.. + */ + if(strncmp(lineptr, "#HttpOnly_", 10) == 0) { + lineptr += 10; + co->httponly = TRUE; + } + + if(lineptr[0]=='#') { + /* don't even try the comments */ + free(co); + return NULL; + } + /* strip off the possible end-of-line characters */ + ptr = strchr(lineptr, '\r'); + if(ptr) + *ptr = 0; /* clear it */ + ptr = strchr(lineptr, '\n'); + if(ptr) + *ptr = 0; /* clear it */ + + firstptr = strtok_r((char *)lineptr, "\t", &tok_buf); /* tokenize on TAB */ + + /* + * Now loop through the fields and init the struct we already have + * allocated + */ + for(ptr = firstptr, fields = 0; ptr && !badcookie; + ptr = strtok_r(NULL, "\t", &tok_buf), fields++) { + switch(fields) { + case 0: + if(ptr[0]=='.') /* skip preceding dots */ + ptr++; + co->domain = strdup(ptr); + if(!co->domain) + badcookie = TRUE; + break; + case 1: + /* + * flag: A TRUE/FALSE value indicating if all machines within a given + * domain can access the variable. Set TRUE when the cookie says + * .domain.com and to false when the domain is complete www.domain.com + */ + co->tailmatch = strcasecompare(ptr, "TRUE")?TRUE:FALSE; + break; + case 2: + /* The file format allows the path field to remain not filled in */ + if(strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) { + /* only if the path doesn't look like a boolean option! */ + co->path = strdup(ptr); + if(!co->path) + badcookie = TRUE; + else { + co->spath = sanitize_cookie_path(co->path); + if(!co->spath) { + badcookie = TRUE; /* out of memory bad */ + } + } + break; + } + /* this doesn't look like a path, make one up! */ + co->path = strdup("/"); + if(!co->path) + badcookie = TRUE; + co->spath = strdup("/"); + if(!co->spath) + badcookie = TRUE; + fields++; /* add a field and fall down to secure */ + /* FALLTHROUGH */ + case 3: + co->secure = FALSE; + if(strcasecompare(ptr, "TRUE")) { + if(secure || c->running) + co->secure = TRUE; + else + badcookie = TRUE; + } + break; + case 4: + if(curlx_strtoofft(ptr, NULL, 10, &co->expires)) + badcookie = TRUE; + break; + case 5: + co->name = strdup(ptr); + if(!co->name) + badcookie = TRUE; + else { + /* For Netscape file format cookies we check prefix on the name */ + if(strncasecompare("__Secure-", co->name, 9)) + co->prefix |= COOKIE_PREFIX__SECURE; + else if(strncasecompare("__Host-", co->name, 7)) + co->prefix |= COOKIE_PREFIX__HOST; + } + break; + case 6: + co->value = strdup(ptr); + if(!co->value) + badcookie = TRUE; + break; + } + } + if(6 == fields) { + /* we got a cookie with blank contents, fix it */ + co->value = strdup(""); + if(!co->value) + badcookie = TRUE; + else + fields++; + } + + if(!badcookie && (7 != fields)) + /* we did not find the sufficient number of fields */ + badcookie = TRUE; + + if(badcookie) { + freecookie(co); + return NULL; + } + + } + + if(co->prefix & COOKIE_PREFIX__SECURE) { + /* The __Secure- prefix only requires that the cookie be set secure */ + if(!co->secure) { + freecookie(co); + return NULL; + } + } + if(co->prefix & COOKIE_PREFIX__HOST) { + /* + * The __Host- prefix requires the cookie to be secure, have a "/" path + * and not have a domain set. + */ + if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch) + ; + else { + freecookie(co); + return NULL; + } + } + + if(!c->running && /* read from a file */ + c->newsession && /* clean session cookies */ + !co->expires) { /* this is a session cookie since it doesn't expire! */ + freecookie(co); + return NULL; + } + + co->livecookie = c->running; + co->creationtime = ++c->lastct; + + /* + * 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. + */ + + /* at first, remove expired cookies */ + if(!noexpire) + remove_expired(c); + +#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 isn't NULL since the psl code will + * dereference it. + */ + if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) { + bool acceptable = FALSE; + char lcase[256]; + char lcookie[256]; + size_t dlen = strlen(domain); + size_t clen = strlen(co->domain); + if((dlen < sizeof(lcase)) && (clen < sizeof(lcookie))) { + 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 + 1); + Curl_strntolower(lcookie, co->domain, clen + 1); + acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie); + Curl_psl_release(data); + } + else + acceptable = !bad_domain(domain, strlen(domain)); + } + + if(!acceptable) { + infof(data, "cookie '%s' dropped, domain '%s' must not " + "set cookies for '%s'", co->name, domain, co->domain); + freecookie(co); + return NULL; + } + } +#endif + + /* A non-secure cookie may not overlay an existing secure cookie. */ + myhash = cookiehash(co->domain); + clist = c->cookies[myhash]; + while(clist) { + if(strcasecompare(clist->name, co->name)) { + /* the names are identical */ + bool matching_domains = FALSE; + + if(clist->domain && co->domain) { + if(strcasecompare(clist->domain, co->domain)) + /* The domains are identical */ + matching_domains = TRUE; + } + else if(!clist->domain && !co->domain) + matching_domains = TRUE; + + if(matching_domains && /* the domains were identical */ + clist->spath && co->spath && /* both have paths */ + clist->secure && !co->secure && !secure) { + size_t cllen; + const char *sep; + + /* + * A non-secure cookie may not overlay an existing secure cookie. + * For an existing cookie "a" with path "/login", refuse a new + * cookie "a" with for example path "/login/en", while the path + * "/loginhelper" is ok. + */ + + sep = strchr(clist->spath + 1, '/'); + + if(sep) + cllen = sep - clist->spath; + else + cllen = strlen(clist->spath); + + if(strncasecompare(clist->spath, co->spath, cllen)) { + infof(data, "cookie '%s' for domain '%s' dropped, would " + "overlay an existing cookie", co->name, co->domain); + freecookie(co); + return NULL; + } + } + } + + if(!replace_co && strcasecompare(clist->name, co->name)) { + /* the names are identical */ + + if(clist->domain && co->domain) { + if(strcasecompare(clist->domain, co->domain) && + (clist->tailmatch == co->tailmatch)) + /* The domains are identical */ + replace_old = TRUE; + } + else if(!clist->domain && !co->domain) + replace_old = TRUE; + + if(replace_old) { + /* the domains were identical */ + + if(clist->spath && co->spath && + !strcasecompare(clist->spath, co->spath)) + replace_old = FALSE; + else if(!clist->spath != !co->spath) + replace_old = FALSE; + } + + if(replace_old && !co->livecookie && clist->livecookie) { + /* + * Both cookies matched fine, except that the already present cookie is + * "live", which means it was set from a header, while the new one was + * read from a file and thus isn't "live". "live" cookies are preferred + * so the new cookie is freed. + */ + freecookie(co); + return NULL; + } + if(replace_old) { + replace_co = co; + replace_clist = clist; + } + } + lastc = clist; + clist = clist->next; + } + if(replace_co) { + co = replace_co; + clist = replace_clist; + co->next = clist->next; /* get the next-pointer first */ + + /* when replacing, creationtime is kept from old */ + co->creationtime = clist->creationtime; + + /* then free all the old pointers */ + free(clist->name); + free(clist->value); + free(clist->domain); + free(clist->path); + free(clist->spath); + + *clist = *co; /* then store all the new data */ + + free(co); /* free the newly allocated memory */ + co = clist; + } + + if(c->running) + /* Only show this when NOT reading the cookies from a file */ + infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, " + "expire %" CURL_FORMAT_CURL_OFF_T, + replace_old?"Replaced":"Added", co->name, co->value, + co->domain, co->path, co->expires); + + if(!replace_old) { + /* then make the last item point on this new one */ + if(lastc) + lastc->next = co; + else + c->cookies[myhash] = co; + c->numcookies++; /* one more cookie in the jar */ + } + + /* + * Now that we've added a new cookie to the jar, update the expiration + * tracker in case it is the next one to expire. + */ + if(co->expires && (co->expires < c->next_expiration)) + c->next_expiration = co->expires; + + return co; +} + + +/* + * Curl_cookie_init() + * + * Inits a cookie struct to read data from a local file. This is always + * called before any cookies are set. File may be NULL in which case only the + * struct is initialized. Is file is "-" then STDIN is read. + * + * If 'newsession' is TRUE, discard all "session cookies" on read from file. + * + * Note that 'data' might be called as NULL pointer. If data is NULL, 'file' + * will be ignored. + * + * Returns NULL on out of memory. Invalid cookies are ignored. + */ +struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, + const char *file, + struct CookieInfo *inc, + bool newsession) +{ + struct CookieInfo *c; + char *line = NULL; + FILE *handle = NULL; + + if(!inc) { + /* we didn't get a struct, create one */ + c = calloc(1, sizeof(struct CookieInfo)); + if(!c) + return NULL; /* failed to get memory */ + /* + * Initialize the next_expiration time to signal that we don't have enough + * information yet. + */ + c->next_expiration = CURL_OFF_T_MAX; + } + else { + /* we got an already existing one, use that */ + c = inc; + } + c->newsession = newsession; /* new session? */ + + if(data) { + FILE *fp = NULL; + if(file) { + if(!strcmp(file, "-")) + fp = stdin; + else { + fp = fopen(file, "rb"); + if(!fp) + infof(data, "WARNING: failed to open cookie file \"%s\"", file); + else + handle = fp; + } + } + + c->running = FALSE; /* this is not running, this is init */ + if(fp) { + + line = malloc(MAX_COOKIE_LINE); + if(!line) + goto fail; + while(Curl_get_line(line, MAX_COOKIE_LINE, fp)) { + char *lineptr = line; + bool headerline = FALSE; + if(checkprefix("Set-Cookie:", line)) { + /* This is a cookie line, get it! */ + lineptr = &line[11]; + headerline = TRUE; + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; + } + + Curl_cookie_add(data, c, headerline, TRUE, lineptr, NULL, NULL, TRUE); + } + free(line); /* free the line buffer */ + + /* + * Remove expired cookies from the hash. We must make sure to run this + * after reading the file, and not on every cookie. + */ + remove_expired(c); + + if(handle) + fclose(handle); + } + data->state.cookie_engine = TRUE; + } + c->running = TRUE; /* now, we're running */ + + return c; + +fail: + free(line); + /* + * Only clean up if we allocated it here, as the original could still be in + * use by a share handle. + */ + if(!inc) + Curl_cookie_cleanup(c); + if(handle) + fclose(handle); + return NULL; /* out of memory */ +} + +/* + * cookie_sort + * + * Helper function to sort cookies such that the longest path gets before the + * shorter path. Path, domain and name lengths are considered in that order, + * with the creationtime as the tiebreaker. The creationtime is guaranteed to + * be unique per cookie, so we know we will get an ordering at that point. + */ +static int cookie_sort(const void *p1, const void *p2) +{ + struct Cookie *c1 = *(struct Cookie **)p1; + struct Cookie *c2 = *(struct Cookie **)p2; + size_t l1, l2; + + /* 1 - compare cookie path lengths */ + l1 = c1->path ? strlen(c1->path) : 0; + l2 = c2->path ? strlen(c2->path) : 0; + + if(l1 != l2) + return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */ + + /* 2 - compare cookie domain lengths */ + l1 = c1->domain ? strlen(c1->domain) : 0; + l2 = c2->domain ? strlen(c2->domain) : 0; + + if(l1 != l2) + return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */ + + /* 3 - compare cookie name lengths */ + l1 = c1->name ? strlen(c1->name) : 0; + l2 = c2->name ? strlen(c2->name) : 0; + + if(l1 != l2) + return (l2 > l1) ? 1 : -1; + + /* 4 - compare cookie creation time */ + return (c2->creationtime > c1->creationtime) ? 1 : -1; +} + +/* + * cookie_sort_ct + * + * Helper function to sort cookies according to creation time. + */ +static int cookie_sort_ct(const void *p1, const void *p2) +{ + struct Cookie *c1 = *(struct Cookie **)p1; + struct Cookie *c2 = *(struct Cookie **)p2; + + return (c2->creationtime > c1->creationtime) ? 1 : -1; +} + +#define CLONE(field) \ + do { \ + if(src->field) { \ + d->field = strdup(src->field); \ + if(!d->field) \ + goto fail; \ + } \ + } while(0) + +static struct Cookie *dup_cookie(struct Cookie *src) +{ + struct Cookie *d = calloc(1, sizeof(struct Cookie)); + if(d) { + CLONE(domain); + CLONE(path); + CLONE(spath); + CLONE(name); + CLONE(value); + d->expires = src->expires; + d->tailmatch = src->tailmatch; + d->secure = src->secure; + d->livecookie = src->livecookie; + d->httponly = src->httponly; + d->creationtime = src->creationtime; + } + return d; + +fail: + freecookie(d); + return NULL; +} + +/* + * Curl_cookie_getlist + * + * For a given host and path, return a linked list of cookies that the client + * should send to the server if used now. The secure boolean informs the cookie + * if a secure connection is achieved or not. + * + * It shall only return cookies that haven't expired. + */ +struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, + struct CookieInfo *c, + const char *host, const char *path, + bool secure) +{ + struct Cookie *newco; + struct Cookie *co; + struct Cookie *mainco = NULL; + size_t matches = 0; + bool is_ip; + const size_t myhash = cookiehash(host); + + if(!c || !c->cookies[myhash]) + return NULL; /* no cookie struct or no cookies in the struct */ + + /* at first, remove expired cookies */ + remove_expired(c); + + /* check if host is an IP(v4|v6) address */ + is_ip = Curl_host_is_ipnum(host); + + co = c->cookies[myhash]; + + while(co) { + /* if the cookie requires we're secure we must only continue if we are! */ + if(co->secure?secure:TRUE) { + + /* now check if the domain is correct */ + if(!co->domain || + (co->tailmatch && !is_ip && + cookie_tailmatch(co->domain, strlen(co->domain), host)) || + ((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) { + /* + * the right part of the host matches the domain stuff in the + * cookie data + */ + + /* + * now check the left part of the path with the cookies path + * requirement + */ + if(!co->spath || pathmatch(co->spath, path) ) { + + /* + * and now, we know this is a match and we should create an + * entry for the return-linked-list + */ + + newco = dup_cookie(co); + if(newco) { + /* then modify our next */ + newco->next = mainco; + + /* point the main to us */ + mainco = newco; + + matches++; + if(matches >= MAX_COOKIE_SEND_AMOUNT) { + infof(data, "Included max number of cookies (%zu) in request!", + matches); + break; + } + } + else + goto fail; + } + } + } + co = co->next; + } + + if(matches) { + /* + * Now we need to make sure that if there is a name appearing more than + * once, the longest specified path version comes first. To make this + * the swiftest way, we just sort them all based on path length. + */ + struct Cookie **array; + size_t i; + + /* alloc an array and store all cookie pointers */ + array = malloc(sizeof(struct Cookie *) * matches); + if(!array) + goto fail; + + co = mainco; + + for(i = 0; co; co = co->next) + array[i++] = co; + + /* now sort the cookie pointers in path length order */ + qsort(array, matches, sizeof(struct Cookie *), cookie_sort); + + /* remake the linked list order according to the new order */ + + mainco = array[0]; /* start here */ + for(i = 0; i<matches-1; i++) + array[i]->next = array[i + 1]; + array[matches-1]->next = NULL; /* terminate the list */ + + free(array); /* remove the temporary data again */ + } + + return mainco; /* return the new list */ + +fail: + /* failure, clear up the allocated chain and return NULL */ + Curl_cookie_freelist(mainco); + return NULL; +} + +/* + * Curl_cookie_clearall + * + * Clear all existing cookies and reset the counter. + */ +void Curl_cookie_clearall(struct CookieInfo *cookies) +{ + if(cookies) { + unsigned int i; + for(i = 0; i < COOKIE_HASH_SIZE; i++) { + Curl_cookie_freelist(cookies->cookies[i]); + cookies->cookies[i] = NULL; + } + cookies->numcookies = 0; + } +} + +/* + * Curl_cookie_freelist + * + * Free a list of cookies previously returned by Curl_cookie_getlist(); + */ +void Curl_cookie_freelist(struct Cookie *co) +{ + struct Cookie *next; + while(co) { + next = co->next; + freecookie(co); + co = next; + } +} + +/* + * Curl_cookie_clearsess + * + * Free all session cookies in the cookies list. + */ +void Curl_cookie_clearsess(struct CookieInfo *cookies) +{ + struct Cookie *first, *curr, *next, *prev = NULL; + unsigned int i; + + if(!cookies) + return; + + for(i = 0; i < COOKIE_HASH_SIZE; i++) { + if(!cookies->cookies[i]) + continue; + + first = curr = prev = cookies->cookies[i]; + + for(; curr; curr = next) { + next = curr->next; + if(!curr->expires) { + if(first == curr) + first = next; + + if(prev == curr) + prev = next; + else + prev->next = next; + + freecookie(curr); + cookies->numcookies--; + } + else + prev = curr; + } + + cookies->cookies[i] = first; + } +} + +/* + * Curl_cookie_cleanup() + * + * Free a "cookie object" previous created with Curl_cookie_init(). + */ +void Curl_cookie_cleanup(struct CookieInfo *c) +{ + if(c) { + unsigned int i; + for(i = 0; i < COOKIE_HASH_SIZE; i++) + Curl_cookie_freelist(c->cookies[i]); + free(c); /* free the base struct as well */ + } +} + +/* + * get_netscape_format() + * + * Formats a string for Netscape output file, w/o a newline at the end. + * Function returns a char * to a formatted line. The caller is responsible + * for freeing the returned pointer. + */ +static char *get_netscape_format(const struct Cookie *co) +{ + return aprintf( + "%s" /* httponly preamble */ + "%s%s\t" /* domain */ + "%s\t" /* tailmatch */ + "%s\t" /* path */ + "%s\t" /* secure */ + "%" CURL_FORMAT_CURL_OFF_T "\t" /* expires */ + "%s\t" /* name */ + "%s", /* value */ + co->httponly?"#HttpOnly_":"", + /* + * Make sure all domains are prefixed with a dot if they allow + * tailmatching. This is Mozilla-style. + */ + (co->tailmatch && co->domain && co->domain[0] != '.')? ".":"", + co->domain?co->domain:"unknown", + co->tailmatch?"TRUE":"FALSE", + co->path?co->path:"/", + co->secure?"TRUE":"FALSE", + co->expires, + co->name, + co->value?co->value:""); +} + +/* + * cookie_output() + * + * Writes all internally known cookies to the specified file. Specify + * "-" as file name to write to stdout. + * + * The function returns non-zero on write failure. + */ +static CURLcode cookie_output(struct Curl_easy *data, + struct CookieInfo *c, const char *filename) +{ + struct Cookie *co; + FILE *out = NULL; + bool use_stdout = FALSE; + char *tempstore = NULL; + CURLcode error = CURLE_OK; + + if(!c) + /* no cookie engine alive */ + return CURLE_OK; + + /* at first, remove expired cookies */ + remove_expired(c); + + if(!strcmp("-", filename)) { + /* use stdout */ + out = stdout; + use_stdout = TRUE; + } + else { + error = Curl_fopen(data, filename, &out, &tempstore); + if(error) + goto error; + } + + fputs("# Netscape HTTP Cookie File\n" + "# https://curl.se/docs/http-cookies.html\n" + "# This file was generated by libcurl! Edit at your own risk.\n\n", + out); + + if(c->numcookies) { + unsigned int i; + size_t nvalid = 0; + struct Cookie **array; + + array = calloc(1, sizeof(struct Cookie *) * c->numcookies); + if(!array) { + error = CURLE_OUT_OF_MEMORY; + goto error; + } + + /* only sort the cookies with a domain property */ + for(i = 0; i < COOKIE_HASH_SIZE; i++) { + for(co = c->cookies[i]; co; co = co->next) { + if(!co->domain) + continue; + array[nvalid++] = co; + } + } + + qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct); + + for(i = 0; i < nvalid; i++) { + char *format_ptr = get_netscape_format(array[i]); + if(!format_ptr) { + free(array); + error = CURLE_OUT_OF_MEMORY; + goto error; + } + fprintf(out, "%s\n", format_ptr); + free(format_ptr); + } + + free(array); + } + + if(!use_stdout) { + fclose(out); + out = NULL; + if(tempstore && Curl_rename(tempstore, filename)) { + unlink(tempstore); + error = CURLE_WRITE_ERROR; + goto error; + } + } + + /* + * If we reach here we have successfully written a cookie file so there is + * no need to inspect the error, any error case should have jumped into the + * error block below. + */ + free(tempstore); + return CURLE_OK; + +error: + if(out && !use_stdout) + fclose(out); + free(tempstore); + return error; +} + +static struct curl_slist *cookie_list(struct Curl_easy *data) +{ + struct curl_slist *list = NULL; + struct curl_slist *beg; + struct Cookie *c; + char *line; + unsigned int i; + + if(!data->cookies || (data->cookies->numcookies == 0)) + return NULL; + + for(i = 0; i < COOKIE_HASH_SIZE; i++) { + for(c = data->cookies->cookies[i]; c; c = c->next) { + if(!c->domain) + continue; + line = get_netscape_format(c); + if(!line) { + curl_slist_free_all(list); + return NULL; + } + beg = Curl_slist_append_nodup(list, line); + if(!beg) { + free(line); + curl_slist_free_all(list); + return NULL; + } + list = beg; + } + } + + return list; +} + +struct curl_slist *Curl_cookie_list(struct Curl_easy *data) +{ + struct curl_slist *list; + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + list = cookie_list(data); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + return list; +} + +void Curl_flush_cookies(struct Curl_easy *data, bool cleanup) +{ + CURLcode res; + + if(data->set.str[STRING_COOKIEJAR]) { + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + + /* if we have a destination file for all the cookies to get dumped to */ + res = cookie_output(data, data->cookies, data->set.str[STRING_COOKIEJAR]); + if(res) + infof(data, "WARNING: failed to save cookies in %s: %s", + data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res)); + } + else { + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + } + + if(cleanup && (!data->share || (data->cookies != data->share->cookies))) { + Curl_cookie_cleanup(data->cookies); + data->cookies = NULL; + } + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); +} + +#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */ diff --git a/Utilities/cmcurl/lib/cookie.h b/Utilities/cmcurl/lib/cookie.h new file mode 100644 index 0000000..012dd89 --- /dev/null +++ b/Utilities/cmcurl/lib/cookie.h @@ -0,0 +1,138 @@ +#ifndef HEADER_CURL_COOKIE_H +#define HEADER_CURL_COOKIE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#include <curl/curl.h> + +struct Cookie { + struct Cookie *next; /* next in the chain */ + char *name; /* <this> = value */ + char *value; /* name = <this> */ + char *path; /* path = <this> which is in Set-Cookie: */ + char *spath; /* sanitized cookie path */ + char *domain; /* domain = <this> */ + curl_off_t expires; /* expires = <this> */ + bool tailmatch; /* whether we do tail-matching of the domain name */ + bool secure; /* whether the 'secure' keyword was used */ + bool livecookie; /* updated from a server, not a stored file */ + bool httponly; /* true if the httponly directive is present */ + int creationtime; /* time when the cookie was written */ + unsigned char prefix; /* bitmap fields indicating which prefix are set */ +}; + +/* + * Available cookie prefixes, as defined in + * draft-ietf-httpbis-rfc6265bis-02 + */ +#define COOKIE_PREFIX__SECURE (1<<0) +#define COOKIE_PREFIX__HOST (1<<1) + +#define COOKIE_HASH_SIZE 63 + +struct CookieInfo { + /* linked list of cookies we know of */ + struct Cookie *cookies[COOKIE_HASH_SIZE]; + curl_off_t next_expiration; /* the next time at which expiration happens */ + int numcookies; /* number of cookies in the "jar" */ + int lastct; /* last creation-time used in the jar */ + bool running; /* state info, for cookie adding information */ + bool newsession; /* new session, discard session cookies on load */ +}; + +/* The maximum sizes we accept for cookies. RFC 6265 section 6.1 says + "general-use user agents SHOULD provide each of the following minimum + capabilities": + + - At least 4096 bytes per cookie (as measured by the sum of the length of + the cookie's name, value, and attributes). + 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 **/ + +/* The longest we allow a line to be when reading a cookie from a HTTP header + or from a cookie jar */ +#define MAX_COOKIE_LINE 5000 + +/* Maximum length of an incoming cookie name or content we deal with. Longer + cookies are ignored. */ +#define MAX_NAME 4096 + +/* Maximum number of Set-Cookie: lines accepted in a single response. If more + such header lines are received, they are ignored. This value must be less + than 256 since an unsigned char is used to count. */ +#define MAX_SET_COOKIE_AMOUNT 50 + +/** Limits for OUTGOING cookies **/ + +/* Maximum size for an outgoing cookie line libcurl will use in an http + request. This is the default maximum length used in some versions of Apache + httpd. */ +#define MAX_COOKIE_HEADER_LEN 8190 + +/* Maximum number of cookies libcurl will send in a single request, even if + there might be more cookies that match. One reason to cap the number is to + keep the maximum HTTP request within the maximum allowed size. */ +#define MAX_COOKIE_SEND_AMOUNT 150 + +struct Curl_easy; +/* + * Add a cookie to the internal list of cookies. The domain and path arguments + * are only used if the header boolean is TRUE. + */ + +struct Cookie *Curl_cookie_add(struct Curl_easy *data, + struct CookieInfo *c, bool header, + bool noexpiry, const char *lineptr, + const char *domain, const char *path, + bool secure); + +struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, + struct CookieInfo *c, const char *host, + const char *path, bool secure); +void Curl_cookie_freelist(struct Cookie *cookies); +void Curl_cookie_clearall(struct CookieInfo *cookies); +void Curl_cookie_clearsess(struct CookieInfo *cookies); + +#if defined(CURL_DISABLE_HTTP) || defined(CURL_DISABLE_COOKIES) +#define Curl_cookie_list(x) NULL +#define Curl_cookie_loadfiles(x) Curl_nop_stmt +#define Curl_cookie_init(x,y,z,w) NULL +#define Curl_cookie_cleanup(x) Curl_nop_stmt +#define Curl_flush_cookies(x,y) Curl_nop_stmt +#else +void Curl_flush_cookies(struct Curl_easy *data, bool cleanup); +void Curl_cookie_cleanup(struct CookieInfo *c); +struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, + const char *file, struct CookieInfo *inc, + bool newsession); +struct curl_slist *Curl_cookie_list(struct Curl_easy *data); +void Curl_cookie_loadfiles(struct Curl_easy *data); +#endif + +#endif /* HEADER_CURL_COOKIE_H */ diff --git a/Utilities/cmcurl/lib/curl_addrinfo.c b/Utilities/cmcurl/lib/curl_addrinfo.c new file mode 100644 index 0000000..f9211d3 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_addrinfo.c @@ -0,0 +1,592 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN6_H +# include <netinet/in6.h> +#endif +#ifdef HAVE_NETDB_H +# include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif +#ifdef HAVE_SYS_UN_H +# include <sys/un.h> +#endif + +#ifdef __VMS +# include <in.h> +# include <inet.h> +#endif + +#include <stddef.h> + +#include "curl_addrinfo.h" +#include "inet_pton.h" +#include "warnless.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_freeaddrinfo() + * + * This is used to free a linked list of Curl_addrinfo structs along + * with all its associated allocated storage. This function should be + * called once for each successful call to Curl_getaddrinfo_ex() or to + * any function call which actually allocates a Curl_addrinfo struct. + */ + +#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER == 910) && \ + defined(__OPTIMIZE__) && defined(__unix__) && defined(__i386__) + /* workaround icc 9.1 optimizer issue */ +# define vqualifier volatile +#else +# define vqualifier +#endif + +void +Curl_freeaddrinfo(struct Curl_addrinfo *cahead) +{ + struct Curl_addrinfo *vqualifier canext; + struct Curl_addrinfo *ca; + + for(ca = cahead; ca; ca = canext) { + canext = ca->ai_next; + free(ca); + } +} + + +#ifdef HAVE_GETADDRINFO +/* + * Curl_getaddrinfo_ex() + * + * This is a wrapper function around system's getaddrinfo(), with + * the only difference that instead of returning a linked list of + * addrinfo structs this one returns a linked list of Curl_addrinfo + * ones. The memory allocated by this function *MUST* be free'd with + * Curl_freeaddrinfo(). For each successful call to this function + * there must be an associated call later to Curl_freeaddrinfo(). + * + * There should be no single call to system's getaddrinfo() in the + * whole library, any such call should be 'routed' through this one. + */ + +int +Curl_getaddrinfo_ex(const char *nodename, + const char *servname, + const struct addrinfo *hints, + struct Curl_addrinfo **result) +{ + const struct addrinfo *ai; + struct addrinfo *aihead; + struct Curl_addrinfo *cafirst = NULL; + struct Curl_addrinfo *calast = NULL; + struct Curl_addrinfo *ca; + size_t ss_size; + int error; + + *result = NULL; /* assume failure */ + + error = getaddrinfo(nodename, servname, hints, &aihead); + if(error) + return error; + + /* traverse the addrinfo list */ + + for(ai = aihead; ai != NULL; ai = ai->ai_next) { + size_t namelen = ai->ai_canonname ? strlen(ai->ai_canonname) + 1 : 0; + /* ignore elements with unsupported address family, */ + /* settle family-specific sockaddr structure size. */ + if(ai->ai_family == AF_INET) + ss_size = sizeof(struct sockaddr_in); +#ifdef ENABLE_IPV6 + else if(ai->ai_family == AF_INET6) + ss_size = sizeof(struct sockaddr_in6); +#endif + else + continue; + + /* ignore elements without required address info */ + if(!ai->ai_addr || !(ai->ai_addrlen > 0)) + continue; + + /* ignore elements with bogus address size */ + if((size_t)ai->ai_addrlen < ss_size) + continue; + + ca = malloc(sizeof(struct Curl_addrinfo) + ss_size + namelen); + if(!ca) { + error = EAI_MEMORY; + break; + } + + /* copy each structure member individually, member ordering, */ + /* size, or padding might be different for each platform. */ + + ca->ai_flags = ai->ai_flags; + ca->ai_family = ai->ai_family; + ca->ai_socktype = ai->ai_socktype; + ca->ai_protocol = ai->ai_protocol; + ca->ai_addrlen = (curl_socklen_t)ss_size; + ca->ai_addr = NULL; + ca->ai_canonname = NULL; + ca->ai_next = NULL; + + ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo)); + memcpy(ca->ai_addr, ai->ai_addr, ss_size); + + if(namelen) { + ca->ai_canonname = (void *)((char *)ca->ai_addr + ss_size); + memcpy(ca->ai_canonname, ai->ai_canonname, namelen); + } + + /* if the return list is empty, this becomes the first element */ + if(!cafirst) + cafirst = ca; + + /* add this element last in the return list */ + if(calast) + calast->ai_next = ca; + calast = ca; + + } + + /* destroy the addrinfo list */ + if(aihead) + freeaddrinfo(aihead); + + /* if we failed, also destroy the Curl_addrinfo list */ + if(error) { + Curl_freeaddrinfo(cafirst); + cafirst = NULL; + } + else if(!cafirst) { +#ifdef EAI_NONAME + /* rfc3493 conformant */ + error = EAI_NONAME; +#else + /* rfc3493 obsoleted */ + error = EAI_NODATA; +#endif +#ifdef USE_WINSOCK + SET_SOCKERRNO(error); +#endif + } + + *result = cafirst; + + /* This is not a CURLcode */ + return error; +} +#endif /* HAVE_GETADDRINFO */ + + +/* + * Curl_he2ai() + * + * This function returns a pointer to the first element of a newly allocated + * Curl_addrinfo struct linked list filled with the data of a given hostent. + * Curl_addrinfo is meant to work like the addrinfo struct does for a IPv6 + * stack, but usable also for IPv4, all hosts and environments. + * + * The memory allocated by this function *MUST* be free'd later on calling + * Curl_freeaddrinfo(). For each successful call to this function there + * must be an associated call later to Curl_freeaddrinfo(). + * + * Curl_addrinfo defined in "lib/curl_addrinfo.h" + * + * struct Curl_addrinfo { + * int ai_flags; + * int ai_family; + * int ai_socktype; + * int ai_protocol; + * curl_socklen_t ai_addrlen; * Follow rfc3493 struct addrinfo * + * char *ai_canonname; + * struct sockaddr *ai_addr; + * struct Curl_addrinfo *ai_next; + * }; + * + * hostent defined in <netdb.h> + * + * struct hostent { + * char *h_name; + * char **h_aliases; + * int h_addrtype; + * int h_length; + * char **h_addr_list; + * }; + * + * for backward compatibility: + * + * #define h_addr h_addr_list[0] + */ + +struct Curl_addrinfo * +Curl_he2ai(const struct hostent *he, int port) +{ + struct Curl_addrinfo *ai; + struct Curl_addrinfo *prevai = NULL; + struct Curl_addrinfo *firstai = NULL; + struct sockaddr_in *addr; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *addr6; +#endif + CURLcode result = CURLE_OK; + int i; + char *curr; + + if(!he) + /* no input == no output! */ + return NULL; + + DEBUGASSERT((he->h_name != NULL) && (he->h_addr_list != NULL)); + + for(i = 0; (curr = he->h_addr_list[i]) != NULL; i++) { + size_t ss_size; + size_t namelen = strlen(he->h_name) + 1; /* include null-terminator */ +#ifdef ENABLE_IPV6 + if(he->h_addrtype == AF_INET6) + ss_size = sizeof(struct sockaddr_in6); + else +#endif + ss_size = sizeof(struct sockaddr_in); + + /* allocate memory to hold the struct, the address and the name */ + ai = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + namelen); + if(!ai) { + result = CURLE_OUT_OF_MEMORY; + break; + } + /* put the address after the struct */ + ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo)); + /* then put the name after the address */ + ai->ai_canonname = (char *)ai->ai_addr + ss_size; + memcpy(ai->ai_canonname, he->h_name, namelen); + + if(!firstai) + /* store the pointer we want to return from this function */ + firstai = ai; + + if(prevai) + /* make the previous entry point to this */ + prevai->ai_next = ai; + + ai->ai_family = he->h_addrtype; + + /* we return all names as STREAM, so when using this address for TFTP + the type must be ignored and conn->socktype be used instead! */ + ai->ai_socktype = SOCK_STREAM; + + ai->ai_addrlen = (curl_socklen_t)ss_size; + + /* leave the rest of the struct filled with zero */ + + switch(ai->ai_family) { + case AF_INET: + addr = (void *)ai->ai_addr; /* storage area for this info */ + + memcpy(&addr->sin_addr, curr, sizeof(struct in_addr)); + addr->sin_family = (CURL_SA_FAMILY_T)(he->h_addrtype); + addr->sin_port = htons((unsigned short)port); + break; + +#ifdef ENABLE_IPV6 + case AF_INET6: + addr6 = (void *)ai->ai_addr; /* storage area for this info */ + + memcpy(&addr6->sin6_addr, curr, sizeof(struct in6_addr)); + addr6->sin6_family = (CURL_SA_FAMILY_T)(he->h_addrtype); + addr6->sin6_port = htons((unsigned short)port); + break; +#endif + } + + prevai = ai; + } + + if(result) { + Curl_freeaddrinfo(firstai); + firstai = NULL; + } + + return firstai; +} + + +struct namebuff { + struct hostent hostentry; + union { + struct in_addr ina4; +#ifdef ENABLE_IPV6 + struct in6_addr ina6; +#endif + } addrentry; + char *h_addr_list[2]; +}; + + +/* + * Curl_ip2addr() + * + * This function takes an internet address, in binary form, as input parameter + * along with its address family and the string version of the address, and it + * returns a Curl_addrinfo chain filled in correctly with information for the + * given address/host + */ + +struct Curl_addrinfo * +Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port) +{ + struct Curl_addrinfo *ai; + +#if defined(__VMS) && \ + defined(__INITIAL_POINTER_SIZE) && (__INITIAL_POINTER_SIZE == 64) +#pragma pointer_size save +#pragma pointer_size short +#pragma message disable PTRMISMATCH +#endif + + struct hostent *h; + struct namebuff *buf; + char *addrentry; + char *hoststr; + size_t addrsize; + + DEBUGASSERT(inaddr && hostname); + + buf = malloc(sizeof(struct namebuff)); + if(!buf) + return NULL; + + hoststr = strdup(hostname); + if(!hoststr) { + free(buf); + return NULL; + } + + switch(af) { + case AF_INET: + addrsize = sizeof(struct in_addr); + addrentry = (void *)&buf->addrentry.ina4; + memcpy(addrentry, inaddr, sizeof(struct in_addr)); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + addrsize = sizeof(struct in6_addr); + addrentry = (void *)&buf->addrentry.ina6; + memcpy(addrentry, inaddr, sizeof(struct in6_addr)); + break; +#endif + default: + free(hoststr); + free(buf); + return NULL; + } + + h = &buf->hostentry; + h->h_name = hoststr; + h->h_aliases = NULL; + h->h_addrtype = (short)af; + h->h_length = (short)addrsize; + h->h_addr_list = &buf->h_addr_list[0]; + h->h_addr_list[0] = addrentry; + h->h_addr_list[1] = NULL; /* terminate list of entries */ + +#if defined(__VMS) && \ + defined(__INITIAL_POINTER_SIZE) && (__INITIAL_POINTER_SIZE == 64) +#pragma pointer_size restore +#pragma message enable PTRMISMATCH +#endif + + ai = Curl_he2ai(h, port); + + free(hoststr); + free(buf); + + return ai; +} + +/* + * Given an IPv4 or IPv6 dotted string address, this converts it to a proper + * allocated Curl_addrinfo struct and returns it. + */ +struct Curl_addrinfo *Curl_str2addr(char *address, int port) +{ + struct in_addr in; + if(Curl_inet_pton(AF_INET, address, &in) > 0) + /* This is a dotted IP address 123.123.123.123-style */ + return Curl_ip2addr(AF_INET, &in, address, port); +#ifdef ENABLE_IPV6 + { + struct in6_addr in6; + if(Curl_inet_pton(AF_INET6, address, &in6) > 0) + /* This is a dotted IPv6 address ::1-style */ + return Curl_ip2addr(AF_INET6, &in6, address, port); + } +#endif + return NULL; /* bad input format */ +} + +#ifdef USE_UNIX_SOCKETS +/** + * Given a path to a Unix domain socket, return a newly allocated Curl_addrinfo + * struct initialized with this path. + * Set '*longpath' to TRUE if the error is a too long path. + */ +struct Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, + bool abstract) +{ + struct Curl_addrinfo *ai; + struct sockaddr_un *sa_un; + size_t path_len; + + *longpath = FALSE; + + ai = calloc(1, sizeof(struct Curl_addrinfo) + sizeof(struct sockaddr_un)); + if(!ai) + return NULL; + ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo)); + + sa_un = (void *) ai->ai_addr; + sa_un->sun_family = AF_UNIX; + + /* sun_path must be able to store the NUL-terminated path */ + path_len = strlen(path) + 1; + if(path_len > sizeof(sa_un->sun_path)) { + free(ai); + *longpath = TRUE; + return NULL; + } + + ai->ai_family = AF_UNIX; + ai->ai_socktype = SOCK_STREAM; /* assume reliable transport for HTTP */ + ai->ai_addrlen = (curl_socklen_t) + ((offsetof(struct sockaddr_un, sun_path) + path_len) & 0x7FFFFFFF); + + /* Abstract Unix domain socket have NULL prefix instead of suffix */ + if(abstract) + memcpy(sa_un->sun_path + 1, path, path_len - 1); + else + memcpy(sa_un->sun_path, path, path_len); /* copy NUL byte */ + + return ai; +} +#endif + +#if defined(CURLDEBUG) && defined(HAVE_GETADDRINFO) && \ + defined(HAVE_FREEADDRINFO) +/* + * curl_dbg_freeaddrinfo() + * + * This is strictly for memory tracing and are using the same style as the + * family otherwise present in memdebug.c. I put these ones here since they + * require a bunch of structs I didn't want to include in memdebug.c + */ + +void +curl_dbg_freeaddrinfo(struct addrinfo *freethis, + int line, const char *source) +{ + curl_dbg_log("ADDR %s:%d freeaddrinfo(%p)\n", + source, line, (void *)freethis); +#ifdef USE_LWIPSOCK + lwip_freeaddrinfo(freethis); +#else + (freeaddrinfo)(freethis); +#endif +} +#endif /* defined(CURLDEBUG) && defined(HAVE_FREEADDRINFO) */ + + +#if defined(CURLDEBUG) && defined(HAVE_GETADDRINFO) +/* + * curl_dbg_getaddrinfo() + * + * This is strictly for memory tracing and are using the same style as the + * family otherwise present in memdebug.c. I put these ones here since they + * require a bunch of structs I didn't want to include in memdebug.c + */ + +int +curl_dbg_getaddrinfo(const char *hostname, + const char *service, + const struct addrinfo *hints, + struct addrinfo **result, + int line, const char *source) +{ +#ifdef USE_LWIPSOCK + int res = lwip_getaddrinfo(hostname, service, hints, result); +#else + int res = (getaddrinfo)(hostname, service, hints, result); +#endif + if(0 == res) + /* success */ + curl_dbg_log("ADDR %s:%d getaddrinfo() = %p\n", + source, line, (void *)*result); + else + curl_dbg_log("ADDR %s:%d getaddrinfo() failed\n", + source, line); + return res; +} +#endif /* defined(CURLDEBUG) && defined(HAVE_GETADDRINFO) */ + +#if defined(HAVE_GETADDRINFO) && defined(USE_RESOLVE_ON_IPS) +/* + * Work-arounds the sin6_port is always zero bug on iOS 9.3.2 and Mac OS X + * 10.11.5. + */ +void Curl_addrinfo_set_port(struct Curl_addrinfo *addrinfo, int port) +{ + struct Curl_addrinfo *ca; + struct sockaddr_in *addr; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *addr6; +#endif + for(ca = addrinfo; ca != NULL; ca = ca->ai_next) { + switch(ca->ai_family) { + case AF_INET: + addr = (void *)ca->ai_addr; /* storage area for this info */ + addr->sin_port = htons((unsigned short)port); + break; + +#ifdef ENABLE_IPV6 + case AF_INET6: + addr6 = (void *)ca->ai_addr; /* storage area for this info */ + addr6->sin6_port = htons((unsigned short)port); + break; +#endif + } + } +} +#endif diff --git a/Utilities/cmcurl/lib/curl_addrinfo.h b/Utilities/cmcurl/lib/curl_addrinfo.h new file mode 100644 index 0000000..c757c49 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_addrinfo.h @@ -0,0 +1,108 @@ +#ifndef HEADER_CURL_ADDRINFO_H +#define HEADER_CURL_ADDRINFO_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +# include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif + +#ifdef __VMS +# include <in.h> +# include <inet.h> +# include <stdlib.h> +#endif + +/* + * Curl_addrinfo is our internal struct definition that we use to allow + * consistent internal handling of this data. We use this even when the + * system provides an addrinfo structure definition. And we use this for + * all sorts of IPv4 and IPV6 builds. + */ + +struct Curl_addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + curl_socklen_t ai_addrlen; /* Follow rfc3493 struct addrinfo */ + char *ai_canonname; + struct sockaddr *ai_addr; + struct Curl_addrinfo *ai_next; +}; + +void +Curl_freeaddrinfo(struct Curl_addrinfo *cahead); + +#ifdef HAVE_GETADDRINFO +int +Curl_getaddrinfo_ex(const char *nodename, + const char *servname, + const struct addrinfo *hints, + struct Curl_addrinfo **result); +#endif + +struct Curl_addrinfo * +Curl_he2ai(const struct hostent *he, int port); + +struct Curl_addrinfo * +Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port); + +struct Curl_addrinfo *Curl_str2addr(char *dotted, int port); + +#ifdef USE_UNIX_SOCKETS +struct Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, + bool abstract); +#endif + +#if defined(CURLDEBUG) && defined(HAVE_GETADDRINFO) && \ + defined(HAVE_FREEADDRINFO) +void +curl_dbg_freeaddrinfo(struct addrinfo *freethis, int line, const char *source); +#endif + +#if defined(CURLDEBUG) && defined(HAVE_GETADDRINFO) +int +curl_dbg_getaddrinfo(const char *hostname, const char *service, + const struct addrinfo *hints, struct addrinfo **result, + int line, const char *source); +#endif + +#ifdef HAVE_GETADDRINFO +#ifdef USE_RESOLVE_ON_IPS +void Curl_addrinfo_set_port(struct Curl_addrinfo *addrinfo, int port); +#else +#define Curl_addrinfo_set_port(x,y) +#endif +#endif + +#endif /* HEADER_CURL_ADDRINFO_H */ diff --git a/Utilities/cmcurl/lib/curl_base64.h b/Utilities/cmcurl/lib/curl_base64.h new file mode 100644 index 0000000..7f7cd1d --- /dev/null +++ b/Utilities/cmcurl/lib/curl_base64.h @@ -0,0 +1,41 @@ +#ifndef HEADER_CURL_BASE64_H +#define HEADER_CURL_BASE64_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#ifndef BUILDING_LIBCURL +/* this renames functions so that the tool code can use the same code + without getting symbol collisions */ +#define Curl_base64_encode(a,b,c,d) curlx_base64_encode(a,b,c,d) +#define Curl_base64url_encode(a,b,c,d) curlx_base64url_encode(a,b,c,d) +#define Curl_base64_decode(a,b,c) curlx_base64_decode(a,b,c) +#endif + +CURLcode Curl_base64_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen); +CURLcode Curl_base64url_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen); +CURLcode Curl_base64_decode(const char *src, + unsigned char **outptr, size_t *outlen); +#endif /* HEADER_CURL_BASE64_H */ diff --git a/Utilities/cmcurl/lib/curl_config.h.cmake b/Utilities/cmcurl/lib/curl_config.h.cmake new file mode 100644 index 0000000..a3c5af5 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_config.h.cmake @@ -0,0 +1,829 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* lib/curl_config.h.in. Generated somehow by cmake. */ + +#include <cm3p/kwiml/abi.h> + +/* Default SSL backend */ +#cmakedefine CURL_DEFAULT_SSL_BACKEND "${CURL_DEFAULT_SSL_BACKEND}" + +/* disables alt-svc */ +#cmakedefine CURL_DISABLE_ALTSVC 1 + +/* disables cookies support */ +#cmakedefine CURL_DISABLE_COOKIES 1 + +/* disables Basic authentication */ +#cmakedefine CURL_DISABLE_BASIC_AUTH 1 + +/* disables Bearer authentication */ +#cmakedefine CURL_DISABLE_BEARER_AUTH 1 + +/* disables Digest authentication */ +#cmakedefine CURL_DISABLE_DIGEST_AUTH 1 + +/* disables Kerberos authentication */ +#cmakedefine CURL_DISABLE_KERBEROS_AUTH 1 + +/* disables negotiate authentication */ +#cmakedefine CURL_DISABLE_NEGOTIATE_AUTH 1 + +/* disables AWS-SIG4 */ +#cmakedefine CURL_DISABLE_AWS 1 + +/* disables DICT */ +#cmakedefine CURL_DISABLE_DICT 1 + +/* disables DNS-over-HTTPS */ +#cmakedefine CURL_DISABLE_DOH 1 + +/* disables FILE */ +#cmakedefine CURL_DISABLE_FILE 1 + +/* disables form api */ +#cmakedefine CURL_DISABLE_FORM_API 1 + +/* disables FTP */ +#cmakedefine CURL_DISABLE_FTP 1 + +/* disables curl_easy_options API for existing options to curl_easy_setopt */ +#cmakedefine CURL_DISABLE_GETOPTIONS 1 + +/* disables GOPHER */ +#cmakedefine CURL_DISABLE_GOPHER 1 + +/* disables headers-api support */ +#cmakedefine CURL_DISABLE_HEADERS_API 1 + +/* disables HSTS support */ +#cmakedefine CURL_DISABLE_HSTS 1 + +/* disables HTTP */ +#cmakedefine CURL_DISABLE_HTTP 1 + +/* disables IMAP */ +#cmakedefine CURL_DISABLE_IMAP 1 + +/* disables LDAP */ +#cmakedefine CURL_DISABLE_LDAP 1 + +/* disables LDAPS */ +#cmakedefine CURL_DISABLE_LDAPS 1 + +/* disables --libcurl option from the curl tool */ +#cmakedefine CURL_DISABLE_LIBCURL_OPTION 1 + +/* disables MIME support */ +#cmakedefine CURL_DISABLE_MIME 1 + +/* disables local binding support */ +#cmakedefine CURL_DISABLE_BINDLOCAL 1 + +/* disables MQTT */ +#cmakedefine CURL_DISABLE_MQTT 1 + +/* disables netrc parser */ +#cmakedefine CURL_DISABLE_NETRC 1 + +/* disables NTLM support */ +#cmakedefine CURL_DISABLE_NTLM 1 + +/* disables date parsing */ +#cmakedefine CURL_DISABLE_PARSEDATE 1 + +/* disables POP3 */ +#cmakedefine CURL_DISABLE_POP3 1 + +/* disables built-in progress meter */ +#cmakedefine CURL_DISABLE_PROGRESS_METER 1 + +/* disables proxies */ +#cmakedefine CURL_DISABLE_PROXY 1 + +/* disables RTSP */ +#cmakedefine CURL_DISABLE_RTSP 1 + +/* disables SMB */ +#cmakedefine CURL_DISABLE_SMB 1 + +/* disables SMTP */ +#cmakedefine CURL_DISABLE_SMTP 1 + +/* disables use of socketpair for curl_multi_poll */ +#cmakedefine CURL_DISABLE_SOCKETPAIR 1 + +/* disables TELNET */ +#cmakedefine CURL_DISABLE_TELNET 1 + +/* disables TFTP */ +#cmakedefine CURL_DISABLE_TFTP 1 + +/* disables verbose strings */ +#cmakedefine CURL_DISABLE_VERBOSE_STRINGS 1 + +/* to make a symbol visible */ +#cmakedefine CURL_EXTERN_SYMBOL ${CURL_EXTERN_SYMBOL} +/* Ensure using CURL_EXTERN_SYMBOL is possible */ +#ifndef CURL_EXTERN_SYMBOL +#define CURL_EXTERN_SYMBOL +#endif + +/* Allow SMB to work on Windows */ +#cmakedefine USE_WIN32_CRYPTO 1 + +/* Use Windows LDAP implementation */ +#cmakedefine USE_WIN32_LDAP 1 + +/* Define if you want to enable IPv6 support */ +#cmakedefine ENABLE_IPV6 1 + +/* Define to 1 if you have the alarm function. */ +#cmakedefine HAVE_ALARM 1 + +/* Define to 1 if you have the arc4random function. */ +#cmakedefine HAVE_ARC4RANDOM 1 + +/* Define to 1 if you have the <arpa/inet.h> header file. */ +#cmakedefine HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have _Atomic support. */ +#cmakedefine HAVE_ATOMIC 1 + +/* Define to 1 if you have the `fnmatch' function. */ +#cmakedefine HAVE_FNMATCH 1 + +/* Define to 1 if you have the `basename' function. */ +#cmakedefine HAVE_BASENAME 1 + +/* Define to 1 if bool is an available type. */ +#cmakedefine HAVE_BOOL_T 1 + +/* Define to 1 if you have the __builtin_available function. */ +#cmakedefine HAVE_BUILTIN_AVAILABLE 1 + +/* Define to 1 if you have the clock_gettime function and monotonic timer. */ +#cmakedefine HAVE_CLOCK_GETTIME_MONOTONIC 1 + +/* Define to 1 if you have the clock_gettime function and raw monotonic timer. + */ +#cmakedefine HAVE_CLOCK_GETTIME_MONOTONIC_RAW 1 + +/* Define to 1 if you have the `closesocket' function. */ +#cmakedefine HAVE_CLOSESOCKET 1 + +/* Define to 1 if you have the fcntl function. */ +#cmakedefine HAVE_FCNTL 1 + +/* Define to 1 if you have the <fcntl.h> header file. */ +#cmakedefine HAVE_FCNTL_H 1 + +/* Define to 1 if you have a working fcntl O_NONBLOCK function. */ +#cmakedefine HAVE_FCNTL_O_NONBLOCK 1 + +/* Define to 1 if you have the freeaddrinfo function. */ +#cmakedefine HAVE_FREEADDRINFO 1 + +/* Define to 1 if you have the fseeko function. */ +#cmakedefine HAVE_FSEEKO 1 + +/* Define to 1 if you have the fseeko declaration. */ +#cmakedefine HAVE_DECL_FSEEKO 1 + +/* Define to 1 if you have the _fseeki64 function. */ +#cmakedefine HAVE__FSEEKI64 1 + +/* Define to 1 if you have the ftruncate function. */ +#cmakedefine HAVE_FTRUNCATE 1 + +/* Define to 1 if you have a working getaddrinfo function. */ +#cmakedefine HAVE_GETADDRINFO 1 + +/* Define to 1 if the getaddrinfo function is threadsafe. */ +#cmakedefine HAVE_GETADDRINFO_THREADSAFE 1 + +/* Define to 1 if you have the `geteuid' function. */ +#cmakedefine HAVE_GETEUID 1 + +/* Define to 1 if you have the `getppid' function. */ +#cmakedefine HAVE_GETPPID 1 + +/* Define to 1 if you have the gethostbyname_r function. */ +#cmakedefine HAVE_GETHOSTBYNAME_R 1 + +/* gethostbyname_r() takes 3 args */ +#cmakedefine HAVE_GETHOSTBYNAME_R_3 1 + +/* gethostbyname_r() takes 5 args */ +#cmakedefine HAVE_GETHOSTBYNAME_R_5 1 + +/* gethostbyname_r() takes 6 args */ +#cmakedefine HAVE_GETHOSTBYNAME_R_6 1 + +/* Define to 1 if you have the gethostname function. */ +#cmakedefine HAVE_GETHOSTNAME 1 + +/* Define to 1 if you have a working getifaddrs function. */ +#cmakedefine HAVE_GETIFADDRS 1 + +/* Define to 1 if you have the `getpass_r' function. */ +#cmakedefine HAVE_GETPASS_R 1 + +/* Define to 1 if you have the `getpeername' function. */ +#cmakedefine HAVE_GETPEERNAME 1 + +/* Define to 1 if you have the `getsockname' function. */ +#cmakedefine HAVE_GETSOCKNAME 1 + +/* Define to 1 if you have the `if_nametoindex' function. */ +#cmakedefine HAVE_IF_NAMETOINDEX 1 + +/* Define to 1 if you have the `getpwuid' function. */ +#cmakedefine HAVE_GETPWUID 1 + +/* Define to 1 if you have the `getpwuid_r' function. */ +#cmakedefine HAVE_GETPWUID_R 1 + +/* Define to 1 if you have the `getrlimit' function. */ +#cmakedefine HAVE_GETRLIMIT 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#cmakedefine HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have a working glibc-style strerror_r function. */ +#cmakedefine HAVE_GLIBC_STRERROR_R 1 + +/* Define to 1 if you have a working gmtime_r function. */ +#cmakedefine HAVE_GMTIME_R 1 + +/* if you have the gssapi libraries */ +#cmakedefine HAVE_GSSAPI 1 + +/* Define to 1 if you have the <gssapi/gssapi_generic.h> header file. */ +#cmakedefine HAVE_GSSAPI_GSSAPI_GENERIC_H 1 + +/* Define to 1 if you have the <gssapi/gssapi.h> header file. */ +#cmakedefine HAVE_GSSAPI_GSSAPI_H 1 + +/* Define to 1 if you have the <gssapi/gssapi_krb5.h> header file. */ +#cmakedefine HAVE_GSSAPI_GSSAPI_KRB5_H 1 + +/* if you have the GNU gssapi libraries */ +#cmakedefine HAVE_GSSGNU 1 + +/* if you have the Heimdal gssapi libraries */ +#cmakedefine HAVE_GSSHEIMDAL 1 + +/* if you have the MIT gssapi libraries */ +#cmakedefine HAVE_GSSMIT 1 + +/* Define to 1 if you have the `idna_strerror' function. */ +#cmakedefine HAVE_IDNA_STRERROR 1 + +/* Define to 1 if you have the <ifaddrs.h> header file. */ +#cmakedefine HAVE_IFADDRS_H 1 + +/* Define to 1 if you have a IPv6 capable working inet_ntop function. */ +#cmakedefine HAVE_INET_NTOP 1 + +/* Define to 1 if you have a 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 + +/* Define to 1 if symbol `ADDRESS_FAMILY' exists */ +#cmakedefine HAVE_ADDRESS_FAMILY 1 + +/* Define to 1 if you have the ioctlsocket function. */ +#cmakedefine HAVE_IOCTLSOCKET 1 + +/* Define to 1 if you have the IoctlSocket camel case function. */ +#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. */ +#cmakedefine HAVE_IOCTLSOCKET_FIONBIO 1 + +/* Define to 1 if you have a working ioctl FIONBIO function. */ +#cmakedefine HAVE_IOCTL_FIONBIO 1 + +/* Define to 1 if you have a working ioctl SIOCGIFADDR function. */ +#cmakedefine HAVE_IOCTL_SIOCGIFADDR 1 + +/* Define to 1 if you have the <io.h> header file. */ +#cmakedefine HAVE_IO_H 1 + +/* Define to 1 if you have the lber.h header file. */ +#cmakedefine HAVE_LBER_H 1 + +/* Define to 1 if you have the ldap.h header file. */ +#cmakedefine HAVE_LDAP_H 1 + +/* Use LDAPS implementation */ +#cmakedefine HAVE_LDAP_SSL 1 + +/* Define to 1 if you have the ldap_ssl.h header file. */ +#cmakedefine HAVE_LDAP_SSL_H 1 + +/* Define to 1 if you have the `ldap_url_parse' function. */ +#cmakedefine HAVE_LDAP_URL_PARSE 1 + +/* Define to 1 if you have the <libgen.h> header file. */ +#cmakedefine HAVE_LIBGEN_H 1 + +/* Define to 1 if you have the `idn2' library (-lidn2). */ +#cmakedefine HAVE_LIBIDN2 1 + +/* Define to 1 if you have the idn2.h header file. */ +#cmakedefine HAVE_IDN2_H 1 + +/* Define to 1 if you have the `socket' library (-lsocket). */ +#cmakedefine HAVE_LIBSOCKET 1 + +/* Define to 1 if you have the `ssh2' library (-lssh2). */ +#cmakedefine HAVE_LIBSSH2 1 + +/* if zlib is available */ +#cmakedefine HAVE_LIBZ 1 + +/* if brotli is available */ +#cmakedefine HAVE_BROTLI 1 + +/* if zstd is available */ +#cmakedefine HAVE_ZSTD 1 + +/* Define to 1 if you have the <locale.h> header file. */ +#cmakedefine HAVE_LOCALE_H 1 + +/* Define to 1 if the compiler supports the 'long long' data type. */ +#if KWIML_ABI_SIZEOF_LONG_LONG +# define HAVE_LONGLONG 1 +#endif + +/* Define to 1 if you have the 'suseconds_t' data type. */ +#cmakedefine HAVE_SUSECONDS_T 1 + +/* Define to 1 if you have the MSG_NOSIGNAL flag. */ +#cmakedefine HAVE_MSG_NOSIGNAL 1 + +/* Define to 1 if you have the <netdb.h> header file. */ +#cmakedefine HAVE_NETDB_H 1 + +/* Define to 1 if you have the <netinet/in.h> header file. */ +#cmakedefine HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the <netinet/tcp.h> header file. */ +#cmakedefine HAVE_NETINET_TCP_H 1 + +/* Define to 1 if you have the <netinet/udp.h> header file. */ +#cmakedefine HAVE_NETINET_UDP_H 1 + +/* Define to 1 if you have the <linux/tcp.h> header file. */ +#cmakedefine HAVE_LINUX_TCP_H 1 + +/* Define to 1 if you have the <net/if.h> header file. */ +#cmakedefine HAVE_NET_IF_H 1 + +/* if you have an old MIT gssapi library, lacking GSS_C_NT_HOSTBASED_SERVICE */ +#cmakedefine HAVE_OLD_GSSMIT 1 + +/* Define to 1 if you have the `pipe' function. */ +#cmakedefine HAVE_PIPE 1 + +/* If you have a fine poll */ +#cmakedefine HAVE_POLL_FINE 1 + +/* Define to 1 if you have the <poll.h> header file. */ +#cmakedefine HAVE_POLL_H 1 + +/* Define to 1 if you have a working POSIX-style strerror_r function. */ +#cmakedefine HAVE_POSIX_STRERROR_R 1 + +/* Define to 1 if you have the <pthread.h> header file */ +#cmakedefine HAVE_PTHREAD_H 1 + +/* Define to 1 if you have the <pwd.h> header file. */ +#cmakedefine HAVE_PWD_H 1 + +/* Define to 1 if OpenSSL has the `SSL_set0_wbio` function. */ +#cmakedefine HAVE_SSL_SET0_WBIO 1 + +/* 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 + +/* Define to 1 if you have the send function. */ +#cmakedefine HAVE_SEND 1 + +/* Define to 1 if you have the sendmsg function. */ +#cmakedefine HAVE_SENDMSG 1 + +/* Define to 1 if you have the 'fsetxattr' function. */ +#cmakedefine HAVE_FSETXATTR 1 + +/* fsetxattr() takes 5 args */ +#cmakedefine HAVE_FSETXATTR_5 1 + +/* fsetxattr() takes 6 args */ +#cmakedefine HAVE_FSETXATTR_6 1 + +/* Define to 1 if you have the `setlocale' function. */ +#cmakedefine HAVE_SETLOCALE 1 + +/* Define to 1 if you have the `setmode' function. */ +#cmakedefine HAVE_SETMODE 1 + +/* Define to 1 if you have the `setrlimit' function. */ +#cmakedefine HAVE_SETRLIMIT 1 + +/* Define to 1 if you have a working setsockopt SO_NONBLOCK function. */ +#cmakedefine HAVE_SETSOCKOPT_SO_NONBLOCK 1 + +/* Define to 1 if you have the sigaction function. */ +#cmakedefine HAVE_SIGACTION 1 + +/* Define to 1 if you have the siginterrupt function. */ +#cmakedefine HAVE_SIGINTERRUPT 1 + +/* Define to 1 if you have the signal function. */ +#cmakedefine HAVE_SIGNAL 1 + +/* Define to 1 if you have the sigsetjmp function or macro. */ +#cmakedefine HAVE_SIGSETJMP 1 + +/* Define to 1 if you have the `snprintf' function. */ +#cmakedefine HAVE_SNPRINTF 1 + +/* Define to 1 if struct sockaddr_in6 has the sin6_scope_id member */ +#cmakedefine HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 + +/* Define to 1 if you have the `socket' function. */ +#cmakedefine HAVE_SOCKET 1 + +/* Define to 1 if you have the socketpair function. */ +#cmakedefine HAVE_SOCKETPAIR 1 + +/* Define to 1 if you have the <stdatomic.h> header file. */ +#cmakedefine HAVE_STDATOMIC_H 1 + +/* Define to 1 if you have the <stdbool.h> header file. */ +#cmakedefine HAVE_STDBOOL_H 1 + +/* Define to 1 if you have the strcasecmp function. */ +#cmakedefine HAVE_STRCASECMP 1 + +/* Define to 1 if you have the strcmpi function. */ +#cmakedefine HAVE_STRCMPI 1 + +/* Define to 1 if you have the strdup function. */ +#cmakedefine HAVE_STRDUP 1 + +/* Define to 1 if you have the strerror_r function. */ +#cmakedefine HAVE_STRERROR_R 1 + +/* Define to 1 if you have the stricmp function. */ +#cmakedefine HAVE_STRICMP 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#cmakedefine HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <stropts.h> header file. */ +#cmakedefine HAVE_STROPTS_H 1 + +/* Define to 1 if you have the strtok_r function. */ +#cmakedefine HAVE_STRTOK_R 1 + +/* Define to 1 if you have the strtoll function. */ +#cmakedefine HAVE_STRTOLL 1 + +/* Define to 1 if you have the memrchr function. */ +#cmakedefine HAVE_MEMRCHR 1 + +/* if struct sockaddr_storage is defined */ +#cmakedefine HAVE_STRUCT_SOCKADDR_STORAGE 1 + +/* Define to 1 if you have the timeval struct. */ +#cmakedefine HAVE_STRUCT_TIMEVAL 1 + +/* Define to 1 if you have the <sys/filio.h> header file. */ +#cmakedefine HAVE_SYS_FILIO_H 1 + +/* Define to 1 if you have the <sys/wait.h> header file. */ +#cmakedefine HAVE_SYS_WAIT_H 1 + +/* Define to 1 if you have the <sys/ioctl.h> header file. */ +#cmakedefine HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the <sys/param.h> header file. */ +#cmakedefine HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the <sys/poll.h> header file. */ +#cmakedefine HAVE_SYS_POLL_H 1 + +/* Define to 1 if you have the <sys/resource.h> header file. */ +#cmakedefine HAVE_SYS_RESOURCE_H 1 + +/* Define to 1 if you have the <sys/select.h> header file. */ +#cmakedefine HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the <sys/socket.h> header file. */ +#cmakedefine HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the <sys/sockio.h> header file. */ +#cmakedefine HAVE_SYS_SOCKIO_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#cmakedefine HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/time.h> header file. */ +#cmakedefine HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#cmakedefine HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <sys/un.h> header file. */ +#cmakedefine HAVE_SYS_UN_H 1 + +/* Define to 1 if you have the <sys/utime.h> header file. */ +#cmakedefine HAVE_SYS_UTIME_H 1 + +/* Define to 1 if you have the <termios.h> header file. */ +#cmakedefine HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the <termio.h> header file. */ +#cmakedefine HAVE_TERMIO_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `utime' function. */ +#cmakedefine HAVE_UTIME 1 + +/* Define to 1 if you have the `utimes' function. */ +#cmakedefine HAVE_UTIMES 1 + +/* Define to 1 if you have the <utime.h> header file. */ +#cmakedefine HAVE_UTIME_H 1 + +/* Define to 1 if you have the windows.h header file. */ +#cmakedefine HAVE_WINDOWS_H 1 + +/* Define to 1 if you have the winsock2.h header file. */ +#cmakedefine HAVE_WINSOCK2_H 1 + +/* Define this symbol if your OS supports changing the contents of argv */ +#cmakedefine HAVE_WRITABLE_ARGV 1 + +/* Define to 1 if you have the ws2tcpip.h header file. */ +#cmakedefine HAVE_WS2TCPIP_H 1 + +/* Define to 1 if you need the lber.h header file even with ldap.h */ +#cmakedefine NEED_LBER_H 1 + +/* Define to 1 if you need the malloc.h header file even with stdlib.h */ +#cmakedefine NEED_MALLOC_H 1 + +/* Define to 1 if _REENTRANT preprocessor symbol must be defined. */ +#cmakedefine NEED_REENTRANT 1 + +/* cpu-machine-OS */ +#cmakedefine OS ${OS} + +/* Name of package */ +#cmakedefine PACKAGE ${PACKAGE} + +/* Define to the address where bug reports for this package should be sent. */ +#cmakedefine PACKAGE_BUGREPORT ${PACKAGE_BUGREPORT} + +/* Define to the full name of this package. */ +#cmakedefine PACKAGE_NAME ${PACKAGE_NAME} + +/* Define to the full name and version of this package. */ +#cmakedefine PACKAGE_STRING ${PACKAGE_STRING} + +/* Define to the one symbol short name of this package. */ +#cmakedefine PACKAGE_TARNAME ${PACKAGE_TARNAME} + +/* Define to the version of this package. */ +#cmakedefine PACKAGE_VERSION ${PACKAGE_VERSION} + +/* a suitable file to read random data from */ +#cmakedefine RANDOM_FILE "${RANDOM_FILE}" + +/* + 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 +*/ + +/* The size of `int', as computed by sizeof. */ +#define SIZEOF_INT KWIML_ABI_SIZEOF_INT + +/* The size of `short', as computed by sizeof. */ +#define SIZEOF_SHORT KWIML_ABI_SIZEOF_SHORT + +/* The size of `long', as computed by sizeof. */ +#define SIZEOF_LONG KWIML_ABI_SIZEOF_LONG + +/* The size of `long long', as computed by sizeof. */ +#define SIZEOF_LONG_LONG KWIML_ABI_SIZEOF_LONG_LONG + +/* The size of `__int64', as computed by sizeof. */ +#if KWIML_ABI_SIZEOF___INT64 +# define SIZEOF___INT64 KWIML_ABI_SIZEOF___INT64 +#endif + +/* The size of `long long', as computed by sizeof. */ +${SIZEOF_LONG_LONG_CODE} + +/* The size of `off_t', as computed by sizeof. */ +${SIZEOF_OFF_T_CODE} + +/* The size of `curl_off_t', as computed by sizeof. */ +${SIZEOF_CURL_OFF_T_CODE} + +/* The size of `curl_socket_t', as computed by sizeof. */ +${SIZEOF_CURL_SOCKET_T_CODE} + +/* The size of `size_t', as computed by sizeof. */ +${SIZEOF_SIZE_T_CODE} + +/* The size of `ssize_t', as computed by sizeof. */ +${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 want to enable c-ares support */ +#cmakedefine USE_ARES 1 + +/* Define if you want to enable POSIX threaded DNS lookup */ +#cmakedefine USE_THREADS_POSIX 1 + +/* Define if you want to enable WIN32 threaded DNS lookup */ +#cmakedefine USE_THREADS_WIN32 1 + +/* if GnuTLS is enabled */ +#cmakedefine USE_GNUTLS 1 + +/* if Secure Transport is enabled */ +#cmakedefine USE_SECTRANSP 1 + +/* if mbedTLS is enabled */ +#cmakedefine USE_MBEDTLS 1 + +/* if BearSSL is enabled */ +#cmakedefine USE_BEARSSL 1 + +/* if WolfSSL is enabled */ +#cmakedefine USE_WOLFSSL 1 + +/* if libSSH is in use */ +#cmakedefine USE_LIBSSH 1 + +/* if libSSH2 is in use */ +#cmakedefine USE_LIBSSH2 1 + +/* if libPSL is in use */ +#cmakedefine USE_LIBPSL 1 + +/* If you want to build curl with the built-in manual */ +#cmakedefine USE_MANUAL 1 + +/* if you want to use OpenLDAP code instead of legacy ldap implementation */ +#cmakedefine USE_OPENLDAP 1 + +/* if OpenSSL is in use */ +#cmakedefine USE_OPENSSL 1 + +/* Define to 1 if you don't want the OpenSSL configuration to be loaded + automatically */ +#cmakedefine CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG 1 + +/* to enable NGHTTP2 */ +#cmakedefine USE_NGHTTP2 1 + +/* to enable NGTCP2 */ +#cmakedefine USE_NGTCP2 1 + +/* to enable NGHTTP3 */ +#cmakedefine USE_NGHTTP3 1 + +/* to enable quiche */ +#cmakedefine USE_QUICHE 1 + +/* Define to 1 if you have the quiche_conn_set_qlog_fd function. */ +#cmakedefine HAVE_QUICHE_CONN_SET_QLOG_FD 1 + +/* to enable msh3 */ +#cmakedefine USE_MSH3 1 + +/* if Unix domain sockets are enabled */ +#cmakedefine USE_UNIX_SOCKETS 1 + +/* to enable SSPI support */ +#cmakedefine USE_WINDOWS_SSPI 1 + +/* to enable Windows SSL */ +#cmakedefine USE_SCHANNEL 1 + +/* enable multiple SSL backends */ +#cmakedefine CURL_WITH_MULTI_SSL 1 + +/* Version number of package */ +#cmakedefine VERSION ${VERSION} + +/* Define to 1 if OS is AIX. */ +#ifndef _ALL_SOURCE +# undef _ALL_SOURCE +#endif + +/* Number of bits in a file offset, on hosts where this is settable. */ +#cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS} + +/* Define for large files, on AIX-style hosts. */ +#cmakedefine _LARGE_FILES ${_LARGE_FILES} + +/* define this if you need it to compile thread-safe code */ +#cmakedefine _THREAD_SAFE ${_THREAD_SAFE} + +/* Define to empty if `const' does not conform to ANSI C. */ +#cmakedefine const ${const} + +/* Type to use in place of in_addr_t when system does not provide it. */ +#cmakedefine in_addr_t ${in_addr_t} + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to `unsigned int' if <sys/types.h> does not define. */ +#cmakedefine size_t ${size_t} + +/* the signed version of size_t */ +#ifndef SIZEOF_SSIZE_T +# if SIZEOF_LONG == SIZEOF_SIZE_T + typedef long ssize_t; +# elif SIZEOF_LONG_LONG == SIZEOF_SIZE_T + typedef long long ssize_t; +# elif SIZEOF___INT64 == SIZEOF_SIZE_T + typedef __int64 ssize_t; +# else + typedef int ssize_t; +# endif +#endif + +/* Define to 1 if you have the mach_absolute_time function. */ +#cmakedefine HAVE_MACH_ABSOLUTE_TIME 1 + +/* to enable Windows IDN */ +#cmakedefine USE_WIN32_IDN 1 + +/* Define to 1 to enable websocket support. */ +#cmakedefine USE_WEBSOCKETS 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 diff --git a/Utilities/cmcurl/lib/curl_ctype.h b/Utilities/cmcurl/lib/curl_ctype.h new file mode 100644 index 0000000..7f0d0cc --- /dev/null +++ b/Utilities/cmcurl/lib/curl_ctype.h @@ -0,0 +1,51 @@ +#ifndef HEADER_CURL_CTYPE_H +#define HEADER_CURL_CTYPE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#define ISLOWHEXALHA(x) (((x) >= 'a') && ((x) <= 'f')) +#define ISUPHEXALHA(x) (((x) >= 'A') && ((x) <= 'F')) + +#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 ISCNTRL(x) (ISLOWCNTRL(x) || IS7F(x)) +#define ISALPHA(x) (ISLOWER(x) || ISUPPER(x)) +#define ISXDIGIT(x) (ISDIGIT(x) || ISLOWHEXALHA(x) || ISUPHEXALHA(x)) +#define ISALNUM(x) (ISDIGIT(x) || ISLOWER(x) || ISUPPER(x)) +#define ISUPPER(x) (((x) >= 'A') && ((x) <= 'Z')) +#define ISLOWER(x) (((x) >= 'a') && ((x) <= 'z')) +#define ISDIGIT(x) (((x) >= '0') && ((x) <= '9')) +#define ISBLANK(x) (((x) == ' ') || ((x) == '\t')) +#define ISSPACE(x) (ISBLANK(x) || (((x) >= 0xa) && ((x) <= 0x0d))) +#define ISURLPUNTCS(x) (((x) == '-') || ((x) == '.') || ((x) == '_') || \ + ((x) == '~')) +#define ISUNRESERVED(x) (ISALNUM(x) || ISURLPUNTCS(x)) + + +#endif /* HEADER_CURL_CTYPE_H */ diff --git a/Utilities/cmcurl/lib/curl_des.c b/Utilities/cmcurl/lib/curl_des.c new file mode 100644 index 0000000..b77763f --- /dev/null +++ b/Utilities/cmcurl/lib/curl_des.c @@ -0,0 +1,69 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_CURL_NTLM_CORE) && !defined(USE_WOLFSSL) && \ + (defined(USE_GNUTLS) || \ + defined(USE_SECTRANSP) || \ + defined(USE_OS400CRYPTO) || \ + defined(USE_WIN32_CRYPTO)) + +#include "curl_des.h" + +/* + * Curl_des_set_odd_parity() + * + * This is used to apply odd parity to the given byte array. It is typically + * used by when a cryptography engines doesn't have it's own version. + * + * The function is a port of the Java based oddParity() function over at: + * + * https://davenport.sourceforge.net/ntlm.html + * + * Parameters: + * + * bytes [in/out] - The data whose parity bits are to be adjusted for + * odd parity. + * len [out] - The length of the data. + */ +void Curl_des_set_odd_parity(unsigned char *bytes, size_t len) +{ + size_t i; + + for(i = 0; i < len; i++) { + unsigned char b = bytes[i]; + + bool needs_parity = (((b >> 7) ^ (b >> 6) ^ (b >> 5) ^ + (b >> 4) ^ (b >> 3) ^ (b >> 2) ^ + (b >> 1)) & 0x01) == 0; + + if(needs_parity) + bytes[i] |= 0x01; + else + bytes[i] &= 0xfe; + } +} + +#endif diff --git a/Utilities/cmcurl/lib/curl_des.h b/Utilities/cmcurl/lib/curl_des.h new file mode 100644 index 0000000..66525ab --- /dev/null +++ b/Utilities/cmcurl/lib/curl_des.h @@ -0,0 +1,40 @@ +#ifndef HEADER_CURL_DES_H +#define HEADER_CURL_DES_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_CURL_NTLM_CORE) && !defined(USE_WOLFSSL) && \ + (defined(USE_GNUTLS) || \ + defined(USE_SECTRANSP) || \ + defined(USE_OS400CRYPTO) || \ + defined(USE_WIN32_CRYPTO)) + +/* Applies odd parity to the given byte array */ +void Curl_des_set_odd_parity(unsigned char *bytes, size_t length); + +#endif + +#endif /* HEADER_CURL_DES_H */ diff --git a/Utilities/cmcurl/lib/curl_endian.c b/Utilities/cmcurl/lib/curl_endian.c new file mode 100644 index 0000000..11c662a --- /dev/null +++ b/Utilities/cmcurl/lib/curl_endian.c @@ -0,0 +1,84 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_endian.h" + +/* + * Curl_read16_le() + * + * This function converts a 16-bit integer from the little endian format, as + * used in the incoming package to whatever endian format we're using + * natively. + * + * Parameters: + * + * buf [in] - A pointer to a 2 byte buffer. + * + * Returns the integer. + */ +unsigned short Curl_read16_le(const unsigned char *buf) +{ + return (unsigned short)(((unsigned short)buf[0]) | + ((unsigned short)buf[1] << 8)); +} + +/* + * Curl_read32_le() + * + * This function converts a 32-bit integer from the little endian format, as + * used in the incoming package to whatever endian format we're using + * natively. + * + * Parameters: + * + * buf [in] - A pointer to a 4 byte buffer. + * + * Returns the integer. + */ +unsigned int Curl_read32_le(const unsigned char *buf) +{ + return ((unsigned int)buf[0]) | ((unsigned int)buf[1] << 8) | + ((unsigned int)buf[2] << 16) | ((unsigned int)buf[3] << 24); +} + +/* + * Curl_read16_be() + * + * This function converts a 16-bit integer from the big endian format, as + * used in the incoming package to whatever endian format we're using + * natively. + * + * Parameters: + * + * buf [in] - A pointer to a 2 byte buffer. + * + * Returns the integer. + */ +unsigned short Curl_read16_be(const unsigned char *buf) +{ + return (unsigned short)(((unsigned short)buf[0] << 8) | + ((unsigned short)buf[1])); +} diff --git a/Utilities/cmcurl/lib/curl_endian.h b/Utilities/cmcurl/lib/curl_endian.h new file mode 100644 index 0000000..fa28321 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_endian.h @@ -0,0 +1,36 @@ +#ifndef HEADER_CURL_ENDIAN_H +#define HEADER_CURL_ENDIAN_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* Converts a 16-bit integer from little endian */ +unsigned short Curl_read16_le(const unsigned char *buf); + +/* Converts a 32-bit integer from little endian */ +unsigned int Curl_read32_le(const unsigned char *buf); + +/* Converts a 16-bit integer from big endian */ +unsigned short Curl_read16_be(const unsigned char *buf); + +#endif /* HEADER_CURL_ENDIAN_H */ diff --git a/Utilities/cmcurl/lib/curl_fnmatch.c b/Utilities/cmcurl/lib/curl_fnmatch.c new file mode 100644 index 0000000..5f9ca4f --- /dev/null +++ b/Utilities/cmcurl/lib/curl_fnmatch.c @@ -0,0 +1,390 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#ifndef CURL_DISABLE_FTP +#include <curl/curl.h> + +#include "curl_fnmatch.h" +#include "curl_memory.h" + +/* The last #include file should be: */ +#include "memdebug.h" + +#ifndef HAVE_FNMATCH + +#define CURLFNM_CHARSET_LEN (sizeof(char) * 256) +#define CURLFNM_CHSET_SIZE (CURLFNM_CHARSET_LEN + 15) + +#define CURLFNM_NEGATE CURLFNM_CHARSET_LEN + +#define CURLFNM_ALNUM (CURLFNM_CHARSET_LEN + 1) +#define CURLFNM_DIGIT (CURLFNM_CHARSET_LEN + 2) +#define CURLFNM_XDIGIT (CURLFNM_CHARSET_LEN + 3) +#define CURLFNM_ALPHA (CURLFNM_CHARSET_LEN + 4) +#define CURLFNM_PRINT (CURLFNM_CHARSET_LEN + 5) +#define CURLFNM_BLANK (CURLFNM_CHARSET_LEN + 6) +#define CURLFNM_LOWER (CURLFNM_CHARSET_LEN + 7) +#define CURLFNM_GRAPH (CURLFNM_CHARSET_LEN + 8) +#define CURLFNM_SPACE (CURLFNM_CHARSET_LEN + 9) +#define CURLFNM_UPPER (CURLFNM_CHARSET_LEN + 10) + +typedef enum { + CURLFNM_SCHS_DEFAULT = 0, + CURLFNM_SCHS_RIGHTBR, + CURLFNM_SCHS_RIGHTBRLEFTBR +} setcharset_state; + +typedef enum { + CURLFNM_PKW_INIT = 0, + CURLFNM_PKW_DDOT +} parsekey_state; + +typedef enum { + CCLASS_OTHER = 0, + CCLASS_DIGIT, + CCLASS_UPPER, + CCLASS_LOWER +} char_class; + +#define SETCHARSET_OK 1 +#define SETCHARSET_FAIL 0 + +static int parsekeyword(unsigned char **pattern, unsigned char *charset) +{ + parsekey_state state = CURLFNM_PKW_INIT; +#define KEYLEN 10 + char keyword[KEYLEN] = { 0 }; + int i; + unsigned char *p = *pattern; + bool found = FALSE; + for(i = 0; !found; i++) { + char c = *p++; + if(i >= KEYLEN) + return SETCHARSET_FAIL; + switch(state) { + case CURLFNM_PKW_INIT: + if(ISLOWER(c)) + keyword[i] = c; + else if(c == ':') + state = CURLFNM_PKW_DDOT; + else + return SETCHARSET_FAIL; + break; + case CURLFNM_PKW_DDOT: + if(c == ']') + found = TRUE; + else + return SETCHARSET_FAIL; + } + } +#undef KEYLEN + + *pattern = p; /* move caller's pattern pointer */ + if(strcmp(keyword, "digit") == 0) + charset[CURLFNM_DIGIT] = 1; + else if(strcmp(keyword, "alnum") == 0) + charset[CURLFNM_ALNUM] = 1; + else if(strcmp(keyword, "alpha") == 0) + charset[CURLFNM_ALPHA] = 1; + else if(strcmp(keyword, "xdigit") == 0) + charset[CURLFNM_XDIGIT] = 1; + else if(strcmp(keyword, "print") == 0) + charset[CURLFNM_PRINT] = 1; + else if(strcmp(keyword, "graph") == 0) + charset[CURLFNM_GRAPH] = 1; + else if(strcmp(keyword, "space") == 0) + charset[CURLFNM_SPACE] = 1; + else if(strcmp(keyword, "blank") == 0) + charset[CURLFNM_BLANK] = 1; + else if(strcmp(keyword, "upper") == 0) + charset[CURLFNM_UPPER] = 1; + else if(strcmp(keyword, "lower") == 0) + charset[CURLFNM_LOWER] = 1; + else + return SETCHARSET_FAIL; + return SETCHARSET_OK; +} + +/* Return the character class. */ +static char_class charclass(unsigned char c) +{ + if(ISUPPER(c)) + return CCLASS_UPPER; + if(ISLOWER(c)) + return CCLASS_LOWER; + if(ISDIGIT(c)) + return CCLASS_DIGIT; + return CCLASS_OTHER; +} + +/* Include a character or a range in set. */ +static void setcharorrange(unsigned char **pp, unsigned char *charset) +{ + unsigned char *p = (*pp)++; + unsigned char c = *p++; + + charset[c] = 1; + if(ISALNUM(c) && *p++ == '-') { + char_class cc = charclass(c); + unsigned char endrange = *p++; + + if(endrange == '\\') + endrange = *p++; + if(endrange >= c && charclass(endrange) == cc) { + while(c++ != endrange) + if(charclass(c) == cc) /* Chars in class may be not consecutive. */ + charset[c] = 1; + *pp = p; + } + } +} + +/* returns 1 (true) if pattern is OK, 0 if is bad ("p" is pattern pointer) */ +static int setcharset(unsigned char **p, unsigned char *charset) +{ + setcharset_state state = CURLFNM_SCHS_DEFAULT; + bool something_found = FALSE; + unsigned char c; + + memset(charset, 0, CURLFNM_CHSET_SIZE); + for(;;) { + c = **p; + if(!c) + return SETCHARSET_FAIL; + + switch(state) { + case CURLFNM_SCHS_DEFAULT: + if(c == ']') { + if(something_found) + return SETCHARSET_OK; + something_found = TRUE; + state = CURLFNM_SCHS_RIGHTBR; + charset[c] = 1; + (*p)++; + } + else if(c == '[') { + unsigned char *pp = *p + 1; + + if(*pp++ == ':' && parsekeyword(&pp, charset)) + *p = pp; + else { + charset[c] = 1; + (*p)++; + } + something_found = TRUE; + } + else if(c == '^' || c == '!') { + if(!something_found) { + if(charset[CURLFNM_NEGATE]) { + charset[c] = 1; + something_found = TRUE; + } + else + charset[CURLFNM_NEGATE] = 1; /* negate charset */ + } + else + charset[c] = 1; + (*p)++; + } + else if(c == '\\') { + c = *(++(*p)); + if(c) + setcharorrange(p, charset); + else + charset['\\'] = 1; + something_found = TRUE; + } + else { + setcharorrange(p, charset); + something_found = TRUE; + } + break; + case CURLFNM_SCHS_RIGHTBR: + if(c == '[') { + state = CURLFNM_SCHS_RIGHTBRLEFTBR; + charset[c] = 1; + (*p)++; + } + else if(c == ']') { + return SETCHARSET_OK; + } + else if(ISPRINT(c)) { + charset[c] = 1; + (*p)++; + state = CURLFNM_SCHS_DEFAULT; + } + else + /* used 'goto fail' instead of 'return SETCHARSET_FAIL' to avoid a + * nonsense warning 'statement not reached' at end of the fnc when + * compiling on Solaris */ + goto fail; + break; + case CURLFNM_SCHS_RIGHTBRLEFTBR: + if(c == ']') + return SETCHARSET_OK; + state = CURLFNM_SCHS_DEFAULT; + charset[c] = 1; + (*p)++; + break; + } + } +fail: + return SETCHARSET_FAIL; +} + +static int loop(const unsigned char *pattern, const unsigned char *string, + int maxstars) +{ + unsigned char *p = (unsigned char *)pattern; + unsigned char *s = (unsigned char *)string; + unsigned char charset[CURLFNM_CHSET_SIZE] = { 0 }; + + for(;;) { + unsigned char *pp; + + switch(*p) { + case '*': + if(!maxstars) + return CURL_FNMATCH_NOMATCH; + /* Regroup consecutive stars and question marks. This can be done because + '*?*?*' can be expressed as '??*'. */ + for(;;) { + if(*++p == '\0') + return CURL_FNMATCH_MATCH; + if(*p == '?') { + if(!*s++) + return CURL_FNMATCH_NOMATCH; + } + else if(*p != '*') + break; + } + /* Skip string characters until we find a match with pattern suffix. */ + for(maxstars--; *s; s++) { + if(loop(p, s, maxstars) == CURL_FNMATCH_MATCH) + return CURL_FNMATCH_MATCH; + } + return CURL_FNMATCH_NOMATCH; + case '?': + if(!*s) + return CURL_FNMATCH_NOMATCH; + s++; + p++; + break; + case '\0': + return *s? CURL_FNMATCH_NOMATCH: CURL_FNMATCH_MATCH; + case '\\': + if(p[1]) + p++; + if(*s++ != *p++) + return CURL_FNMATCH_NOMATCH; + break; + case '[': + pp = p + 1; /* Copy in case of syntax error in set. */ + if(setcharset(&pp, charset)) { + int found = FALSE; + if(!*s) + return CURL_FNMATCH_NOMATCH; + if(charset[(unsigned int)*s]) + found = TRUE; + else if(charset[CURLFNM_ALNUM]) + found = ISALNUM(*s); + else if(charset[CURLFNM_ALPHA]) + found = ISALPHA(*s); + else if(charset[CURLFNM_DIGIT]) + found = ISDIGIT(*s); + else if(charset[CURLFNM_XDIGIT]) + found = ISXDIGIT(*s); + else if(charset[CURLFNM_PRINT]) + found = ISPRINT(*s); + else if(charset[CURLFNM_SPACE]) + found = ISSPACE(*s); + else if(charset[CURLFNM_UPPER]) + found = ISUPPER(*s); + else if(charset[CURLFNM_LOWER]) + found = ISLOWER(*s); + else if(charset[CURLFNM_BLANK]) + found = ISBLANK(*s); + else if(charset[CURLFNM_GRAPH]) + found = ISGRAPH(*s); + + if(charset[CURLFNM_NEGATE]) + found = !found; + + if(!found) + return CURL_FNMATCH_NOMATCH; + p = pp + 1; + s++; + break; + } + /* Syntax error in set; mismatch! */ + return CURL_FNMATCH_NOMATCH; + + default: + if(*p++ != *s++) + return CURL_FNMATCH_NOMATCH; + break; + } + } +} + +/* + * @unittest: 1307 + */ +int Curl_fnmatch(void *ptr, const char *pattern, const char *string) +{ + (void)ptr; /* the argument is specified by the curl_fnmatch_callback + prototype, but not used by Curl_fnmatch() */ + if(!pattern || !string) { + return CURL_FNMATCH_FAIL; + } + return loop((unsigned char *)pattern, (unsigned char *)string, 2); +} +#else +#include <fnmatch.h> +/* + * @unittest: 1307 + */ +int Curl_fnmatch(void *ptr, const char *pattern, const char *string) +{ + (void)ptr; /* the argument is specified by the curl_fnmatch_callback + prototype, but not used by Curl_fnmatch() */ + if(!pattern || !string) { + return CURL_FNMATCH_FAIL; + } + + switch(fnmatch(pattern, string, 0)) { + case 0: + return CURL_FNMATCH_MATCH; + case FNM_NOMATCH: + return CURL_FNMATCH_NOMATCH; + default: + return CURL_FNMATCH_FAIL; + } + /* not reached */ +} + +#endif + +#endif /* if FTP is disabled */ diff --git a/Utilities/cmcurl/lib/curl_fnmatch.h b/Utilities/cmcurl/lib/curl_fnmatch.h new file mode 100644 index 0000000..595646f --- /dev/null +++ b/Utilities/cmcurl/lib/curl_fnmatch.h @@ -0,0 +1,46 @@ +#ifndef HEADER_CURL_FNMATCH_H +#define HEADER_CURL_FNMATCH_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#define CURL_FNMATCH_MATCH 0 +#define CURL_FNMATCH_NOMATCH 1 +#define CURL_FNMATCH_FAIL 2 + +/* default pattern matching function + * ================================= + * Implemented with recursive backtracking, if you want to use Curl_fnmatch, + * please note that there is not implemented UTF/UNICODE support. + * + * Implemented features: + * '?' notation, does not match UTF characters + * '*' can also work with UTF string + * [a-zA-Z0-9] enumeration support + * + * keywords: alnum, digit, xdigit, alpha, print, blank, lower, graph, space + * and upper (use as "[[:alnum:]]") + */ +int Curl_fnmatch(void *ptr, const char *pattern, const char *string); + +#endif /* HEADER_CURL_FNMATCH_H */ diff --git a/Utilities/cmcurl/lib/curl_get_line.c b/Utilities/cmcurl/lib/curl_get_line.c new file mode 100644 index 0000000..686abe7 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_get_line.c @@ -0,0 +1,86 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_COOKIES) || !defined(CURL_DISABLE_ALTSVC) || \ + !defined(CURL_DISABLE_HSTS) || !defined(CURL_DISABLE_NETRC) + +#include "curl_get_line.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/* + * Curl_get_line() makes sure to only return complete whole lines that fit in + * 'len' bytes and end with a newline. + */ +char *Curl_get_line(char *buf, int len, FILE *input) +{ + bool partial = FALSE; + while(1) { + char *b = fgets(buf, len, input); + + if(b) { + size_t rlen = strlen(b); + + if(!rlen) + break; + + if(b[rlen-1] == '\n') { + /* b is \n terminated */ + if(partial) { + partial = FALSE; + continue; + } + return b; + } + else if(feof(input)) { + if(partial) + /* Line is already too large to return, ignore rest */ + break; + + if(rlen + 1 < (size_t) len) { + /* b is EOF terminated, insert missing \n */ + b[rlen] = '\n'; + b[rlen + 1] = '\0'; + return b; + } + else + /* Maximum buffersize reached + EOF + * This line is impossible to add a \n to so we'll ignore it + */ + break; + } + else + /* Maximum buffersize reached */ + partial = TRUE; + } + else + break; + } + return NULL; +} + +#endif /* if not disabled */ diff --git a/Utilities/cmcurl/lib/curl_get_line.h b/Utilities/cmcurl/lib/curl_get_line.h new file mode 100644 index 0000000..0ff32c5 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_get_line.h @@ -0,0 +1,31 @@ +#ifndef HEADER_CURL_GET_LINE_H +#define HEADER_CURL_GET_LINE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* get_line() makes sure to only return complete whole lines that fit in 'len' + * bytes and end with a newline. */ +char *Curl_get_line(char *buf, int len, FILE *input); + +#endif /* HEADER_CURL_GET_LINE_H */ diff --git a/Utilities/cmcurl/lib/curl_gethostname.c b/Utilities/cmcurl/lib/curl_gethostname.c new file mode 100644 index 0000000..706b2e6 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_gethostname.c @@ -0,0 +1,102 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_gethostname.h" + +/* + * Curl_gethostname() is a wrapper around gethostname() which allows + * overriding the host name that the function would normally return. + * This capability is used by the test suite to verify exact matching + * of NTLM authentication, which exercises libcurl's MD4 and DES code + * as well as by the SMTP module when a hostname is not provided. + * + * For libcurl debug enabled builds host name overriding takes place + * when environment variable CURL_GETHOSTNAME is set, using the value + * held by the variable to override returned host name. + * + * Note: The function always returns the un-qualified hostname rather + * than being provider dependent. + * + * For libcurl shared library release builds the test suite preloads + * another shared library named libhostname using the LD_PRELOAD + * mechanism which intercepts, and might override, the gethostname() + * function call. In this case a given platform must support the + * LD_PRELOAD mechanism and additionally have environment variable + * CURL_GETHOSTNAME set in order to override the returned host name. + * + * For libcurl static library release builds no overriding takes place. + */ + +int Curl_gethostname(char * const name, GETHOSTNAME_TYPE_ARG2 namelen) +{ +#ifndef HAVE_GETHOSTNAME + + /* Allow compilation and return failure when unavailable */ + (void) name; + (void) namelen; + return -1; + +#else + int err; + char *dot; + +#ifdef DEBUGBUILD + + /* Override host name when environment variable CURL_GETHOSTNAME is set */ + const char *force_hostname = getenv("CURL_GETHOSTNAME"); + if(force_hostname) { + strncpy(name, force_hostname, namelen); + err = 0; + } + else { + name[0] = '\0'; + err = gethostname(name, namelen); + } + +#else /* DEBUGBUILD */ + + /* The call to system's gethostname() might get intercepted by the + libhostname library when libcurl is built as a non-debug shared + library when running the test suite. */ + name[0] = '\0'; + err = gethostname(name, namelen); + +#endif + + name[namelen - 1] = '\0'; + + if(err) + return err; + + /* Truncate domain, leave only machine name */ + dot = strchr(name, '.'); + if(dot) + *dot = '\0'; + + return 0; +#endif + +} diff --git a/Utilities/cmcurl/lib/curl_gethostname.h b/Utilities/cmcurl/lib/curl_gethostname.h new file mode 100644 index 0000000..9281d9c --- /dev/null +++ b/Utilities/cmcurl/lib/curl_gethostname.h @@ -0,0 +1,33 @@ +#ifndef HEADER_CURL_GETHOSTNAME_H +#define HEADER_CURL_GETHOSTNAME_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* Hostname buffer size */ +#define HOSTNAME_MAX 1024 + +/* This returns the local machine's un-qualified hostname */ +int Curl_gethostname(char * const name, GETHOSTNAME_TYPE_ARG2 namelen); + +#endif /* HEADER_CURL_GETHOSTNAME_H */ diff --git a/Utilities/cmcurl/lib/curl_gssapi.c b/Utilities/cmcurl/lib/curl_gssapi.c new file mode 100644 index 0000000..c6fe125 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_gssapi.c @@ -0,0 +1,152 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_GSSAPI + +#include "curl_gssapi.h" +#include "sendf.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#if defined(__GNUC__) +#define CURL_ALIGN8 __attribute__ ((aligned(8))) +#else +#define CURL_ALIGN8 +#endif + +gss_OID_desc Curl_spnego_mech_oid CURL_ALIGN8 = { + 6, (char *)"\x2b\x06\x01\x05\x05\x02" +}; +gss_OID_desc Curl_krb5_mech_oid CURL_ALIGN8 = { + 9, (char *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" +}; + +OM_uint32 Curl_gss_init_sec_context( + struct Curl_easy *data, + OM_uint32 *minor_status, + gss_ctx_id_t *context, + gss_name_t target_name, + gss_OID mech_type, + gss_channel_bindings_t input_chan_bindings, + gss_buffer_t input_token, + gss_buffer_t output_token, + const bool mutual_auth, + OM_uint32 *ret_flags) +{ + OM_uint32 req_flags = GSS_C_REPLAY_FLAG; + + if(mutual_auth) + req_flags |= GSS_C_MUTUAL_FLAG; + + if(data->set.gssapi_delegation & CURLGSSAPI_DELEGATION_POLICY_FLAG) { +#ifdef GSS_C_DELEG_POLICY_FLAG + req_flags |= GSS_C_DELEG_POLICY_FLAG; +#else + infof(data, "WARNING: support for CURLGSSAPI_DELEGATION_POLICY_FLAG not " + "compiled in"); +#endif + } + + if(data->set.gssapi_delegation & CURLGSSAPI_DELEGATION_FLAG) + req_flags |= GSS_C_DELEG_FLAG; + + return gss_init_sec_context(minor_status, + GSS_C_NO_CREDENTIAL, /* cred_handle */ + context, + target_name, + mech_type, + req_flags, + 0, /* time_req */ + input_chan_bindings, + input_token, + NULL, /* actual_mech_type */ + output_token, + ret_flags, + NULL /* time_rec */); +} + +#define GSS_LOG_BUFFER_LEN 1024 +static size_t display_gss_error(OM_uint32 status, int type, + char *buf, size_t len) { + OM_uint32 maj_stat; + OM_uint32 min_stat; + OM_uint32 msg_ctx = 0; + gss_buffer_desc status_string = GSS_C_EMPTY_BUFFER; + + do { + maj_stat = gss_display_status(&min_stat, + status, + type, + GSS_C_NO_OID, + &msg_ctx, + &status_string); + if(maj_stat == GSS_S_COMPLETE && status_string.length > 0) { + if(GSS_LOG_BUFFER_LEN > len + status_string.length + 3) { + len += msnprintf(buf + len, GSS_LOG_BUFFER_LEN - len, + "%.*s. ", (int)status_string.length, + (char *)status_string.value); + } + } + gss_release_buffer(&min_stat, &status_string); + } while(!GSS_ERROR(maj_stat) && msg_ctx); + + return len; +} + +/* + * Curl_gss_log_error() + * + * This is used to log a GSS-API error status. + * + * Parameters: + * + * data [in] - The session handle. + * prefix [in] - The prefix of the log message. + * major [in] - The major status code. + * minor [in] - The minor status code. + */ +void Curl_gss_log_error(struct Curl_easy *data, const char *prefix, + OM_uint32 major, OM_uint32 minor) +{ + char buf[GSS_LOG_BUFFER_LEN]; + size_t len = 0; + + if(major != GSS_S_FAILURE) + len = display_gss_error(major, GSS_C_GSS_CODE, buf, len); + + display_gss_error(minor, GSS_C_MECH_CODE, buf, len); + + infof(data, "%s%s", prefix, buf); +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)data; + (void)prefix; +#endif +} + +#endif /* HAVE_GSSAPI */ diff --git a/Utilities/cmcurl/lib/curl_gssapi.h b/Utilities/cmcurl/lib/curl_gssapi.h new file mode 100644 index 0000000..7b9a534 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_gssapi.h @@ -0,0 +1,63 @@ +#ifndef HEADER_CURL_GSSAPI_H +#define HEADER_CURL_GSSAPI_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "urldata.h" + +#ifdef HAVE_GSSAPI +extern gss_OID_desc Curl_spnego_mech_oid; +extern gss_OID_desc Curl_krb5_mech_oid; + +/* Common method for using GSS-API */ +OM_uint32 Curl_gss_init_sec_context( + struct Curl_easy *data, + OM_uint32 *minor_status, + gss_ctx_id_t *context, + gss_name_t target_name, + gss_OID mech_type, + gss_channel_bindings_t input_chan_bindings, + gss_buffer_t input_token, + gss_buffer_t output_token, + const bool mutual_auth, + OM_uint32 *ret_flags); + +/* Helper to log a GSS-API error status */ +void Curl_gss_log_error(struct Curl_easy *data, const char *prefix, + OM_uint32 major, OM_uint32 minor); + +/* Provide some definitions missing in old headers */ +#ifdef HAVE_OLD_GSSMIT +#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name +#define NCOMPAT 1 +#endif + +/* Define our privacy and integrity protection values */ +#define GSSAUTH_P_NONE 1 +#define GSSAUTH_P_INTEGRITY 2 +#define GSSAUTH_P_PRIVACY 4 + +#endif /* HAVE_GSSAPI */ +#endif /* HEADER_CURL_GSSAPI_H */ diff --git a/Utilities/cmcurl/lib/curl_hmac.h b/Utilities/cmcurl/lib/curl_hmac.h new file mode 100644 index 0000000..7a5387a --- /dev/null +++ b/Utilities/cmcurl/lib/curl_hmac.h @@ -0,0 +1,78 @@ +#ifndef HEADER_CURL_HMAC_H +#define HEADER_CURL_HMAC_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ + || !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) \ + || defined(USE_LIBSSH2) + +#include <curl/curl.h> + +#define HMAC_MD5_LENGTH 16 + +typedef CURLcode (* HMAC_hinit_func)(void *context); +typedef void (* HMAC_hupdate_func)(void *context, + const unsigned char *data, + unsigned int len); +typedef void (* HMAC_hfinal_func)(unsigned char *result, void *context); + + +/* Per-hash function HMAC parameters. */ +struct HMAC_params { + HMAC_hinit_func + hmac_hinit; /* Initialize context procedure. */ + HMAC_hupdate_func hmac_hupdate; /* Update context with data. */ + HMAC_hfinal_func hmac_hfinal; /* Get final result procedure. */ + unsigned int hmac_ctxtsize; /* Context structure size. */ + unsigned int hmac_maxkeylen; /* Maximum key length (bytes). */ + unsigned int hmac_resultlen; /* Result length (bytes). */ +}; + + +/* HMAC computation context. */ +struct HMAC_context { + const struct HMAC_params *hmac_hash; /* Hash function definition. */ + void *hmac_hashctxt1; /* Hash function context 1. */ + void *hmac_hashctxt2; /* Hash function context 2. */ +}; + + +/* Prototypes. */ +struct HMAC_context *Curl_HMAC_init(const struct HMAC_params *hashparams, + const unsigned char *key, + unsigned int keylen); +int Curl_HMAC_update(struct HMAC_context *context, + const unsigned char *data, + unsigned int len); +int Curl_HMAC_final(struct HMAC_context *context, unsigned char *result); + +CURLcode Curl_hmacit(const struct HMAC_params *hashparams, + const unsigned char *key, const size_t keylen, + const unsigned char *data, const size_t datalen, + unsigned char *output); + +#endif + +#endif /* HEADER_CURL_HMAC_H */ diff --git a/Utilities/cmcurl/lib/curl_krb5.h b/Utilities/cmcurl/lib/curl_krb5.h new file mode 100644 index 0000000..ccf6b96 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_krb5.h @@ -0,0 +1,52 @@ +#ifndef HEADER_CURL_KRB5_H +#define HEADER_CURL_KRB5_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +struct Curl_sec_client_mech { + const char *name; + size_t size; + int (*init)(void *); + int (*auth)(void *, struct Curl_easy *data, struct connectdata *); + void (*end)(void *); + int (*check_prot)(void *, int); + int (*encode)(void *, const void *, int, int, void **); + int (*decode)(void *, void *, int, int, struct connectdata *); +}; + +#define AUTH_OK 0 +#define AUTH_CONTINUE 1 +#define AUTH_ERROR 2 + +#ifdef HAVE_GSSAPI +int Curl_sec_read_msg(struct Curl_easy *data, struct connectdata *conn, char *, + enum protection_level); +void Curl_sec_end(struct connectdata *); +CURLcode Curl_sec_login(struct Curl_easy *, struct connectdata *); +int Curl_sec_request_prot(struct connectdata *conn, const char *level); +#else +#define Curl_sec_end(x) +#endif + +#endif /* HEADER_CURL_KRB5_H */ diff --git a/Utilities/cmcurl/lib/curl_ldap.h b/Utilities/cmcurl/lib/curl_ldap.h new file mode 100644 index 0000000..8a1d807 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_ldap.h @@ -0,0 +1,36 @@ +#ifndef HEADER_CURL_LDAP_H +#define HEADER_CURL_LDAP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#ifndef CURL_DISABLE_LDAP +extern const struct Curl_handler Curl_handler_ldap; + +#if !defined(CURL_DISABLE_LDAPS) && \ + ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ + (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) +extern const struct Curl_handler Curl_handler_ldaps; +#endif + +#endif +#endif /* HEADER_CURL_LDAP_H */ diff --git a/Utilities/cmcurl/lib/curl_md4.h b/Utilities/cmcurl/lib/curl_md4.h new file mode 100644 index 0000000..4706e49 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_md4.h @@ -0,0 +1,39 @@ +#ifndef HEADER_CURL_MD4_H +#define HEADER_CURL_MD4_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include <curl/curl.h> + +#if defined(USE_CURL_NTLM_CORE) + +#define MD4_DIGEST_LENGTH 16 + +CURLcode Curl_md4it(unsigned char *output, const unsigned char *input, + const size_t len); + +#endif /* defined(USE_CURL_NTLM_CORE) */ + +#endif /* HEADER_CURL_MD4_H */ diff --git a/Utilities/cmcurl/lib/curl_md5.h b/Utilities/cmcurl/lib/curl_md5.h new file mode 100644 index 0000000..61671c3 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_md5.h @@ -0,0 +1,67 @@ +#ifndef HEADER_CURL_MD5_H +#define HEADER_CURL_MD5_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ + || !defined(CURL_DISABLE_DIGEST_AUTH) + +#include "curl_hmac.h" + +#define MD5_DIGEST_LEN 16 + +typedef CURLcode (* Curl_MD5_init_func)(void *context); +typedef void (* Curl_MD5_update_func)(void *context, + const unsigned char *data, + unsigned int len); +typedef void (* Curl_MD5_final_func)(unsigned char *result, void *context); + +struct MD5_params { + Curl_MD5_init_func md5_init_func; /* Initialize context procedure */ + Curl_MD5_update_func md5_update_func; /* Update context with data */ + Curl_MD5_final_func md5_final_func; /* Get final result procedure */ + unsigned int md5_ctxtsize; /* Context structure size */ + unsigned int md5_resultlen; /* Result length (bytes) */ +}; + +struct MD5_context { + const struct MD5_params *md5_hash; /* Hash function definition */ + void *md5_hashctx; /* Hash function context */ +}; + +extern const struct MD5_params Curl_DIGEST_MD5[1]; +extern const struct HMAC_params Curl_HMAC_MD5[1]; + +CURLcode Curl_md5it(unsigned char *output, const unsigned char *input, + const size_t len); + +struct MD5_context *Curl_MD5_init(const struct MD5_params *md5params); +CURLcode Curl_MD5_update(struct MD5_context *context, + const unsigned char *data, + unsigned int len); +CURLcode Curl_MD5_final(struct MD5_context *context, unsigned char *result); + +#endif + +#endif /* HEADER_CURL_MD5_H */ diff --git a/Utilities/cmcurl/lib/curl_memory.h b/Utilities/cmcurl/lib/curl_memory.h new file mode 100644 index 0000000..714ad71 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_memory.h @@ -0,0 +1,178 @@ +#ifndef HEADER_CURL_MEMORY_H +#define HEADER_CURL_MEMORY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Nasty internal details ahead... + * + * File curl_memory.h must be included by _all_ *.c source files + * that use memory related functions strdup, malloc, calloc, realloc + * or free, and given source file is used to build libcurl library. + * It should be included immediately before memdebug.h as the last files + * included to avoid undesired interaction with other memory function + * headers in dependent libraries. + * + * There is nearly no exception to above rule. All libcurl source + * files in 'lib' subdirectory as well as those living deep inside + * 'packages' subdirectories and linked together in order to build + * libcurl library shall follow it. + * + * File lib/strdup.c is an exception, given that it provides a strdup + * clone implementation while using malloc. Extra care needed inside + * this one. + * + * The need for curl_memory.h inclusion is due to libcurl's feature + * of allowing library user to provide memory replacement functions, + * memory callbacks, at runtime with curl_global_init_mem() + * + * Any *.c source file used to build libcurl library that does not + * include curl_memory.h and uses any memory function of the five + * mentioned above will compile without any indication, but it will + * trigger weird memory related issues at runtime. + * + */ + +#ifdef HEADER_CURL_MEMDEBUG_H +/* cleanup after memdebug.h */ + +#ifdef MEMDEBUG_NODEFINES +#ifdef CURLDEBUG + +#undef strdup +#undef malloc +#undef calloc +#undef realloc +#undef free +#undef send +#undef recv + +#ifdef _WIN32 +# ifdef UNICODE +# undef wcsdup +# undef _wcsdup +# undef _tcsdup +# else +# undef _tcsdup +# endif +#endif + +#undef socket +#undef accept +#ifdef HAVE_SOCKETPAIR +#undef socketpair +#endif + +#ifdef HAVE_GETADDRINFO +#if defined(getaddrinfo) && defined(__osf__) +#undef ogetaddrinfo +#else +#undef getaddrinfo +#endif +#endif /* HAVE_GETADDRINFO */ + +#ifdef HAVE_FREEADDRINFO +#undef freeaddrinfo +#endif /* HAVE_FREEADDRINFO */ + +/* sclose is probably already defined, redefine it! */ +#undef sclose +#undef fopen +#undef fdopen +#undef fclose + +#endif /* MEMDEBUG_NODEFINES */ +#endif /* CURLDEBUG */ + +#undef HEADER_CURL_MEMDEBUG_H +#endif /* HEADER_CURL_MEMDEBUG_H */ + +/* +** Following section applies even when CURLDEBUG is not defined. +*/ + +#undef fake_sclose + +#ifndef CURL_DID_MEMORY_FUNC_TYPEDEFS /* only if not already done */ +/* + * The following memory function replacement typedef's are COPIED from + * curl/curl.h and MUST match the originals. We copy them to avoid having to + * include curl/curl.h here. We avoid that include since it includes stdio.h + * and other headers that may get messed up with defines done here. + */ +typedef void *(*curl_malloc_callback)(size_t size); +typedef void (*curl_free_callback)(void *ptr); +typedef void *(*curl_realloc_callback)(void *ptr, size_t size); +typedef char *(*curl_strdup_callback)(const char *str); +typedef void *(*curl_calloc_callback)(size_t nmemb, size_t size); +#define CURL_DID_MEMORY_FUNC_TYPEDEFS +#endif + +extern curl_malloc_callback Curl_cmalloc; +extern curl_free_callback Curl_cfree; +extern curl_realloc_callback Curl_crealloc; +extern curl_strdup_callback Curl_cstrdup; +extern curl_calloc_callback Curl_ccalloc; +#if defined(_WIN32) && defined(UNICODE) +extern curl_wcsdup_callback Curl_cwcsdup; +#endif + +#ifndef CURLDEBUG + +/* + * libcurl's 'memory tracking' system defines strdup, malloc, calloc, + * realloc and free, along with others, in memdebug.h in a different + * way although still using memory callbacks forward declared above. + * When using the 'memory tracking' system (CURLDEBUG defined) we do + * not define here the five memory functions given that definitions + * from memdebug.h are the ones that shall be used. + */ + +#undef strdup +#define strdup(ptr) Curl_cstrdup(ptr) +#undef malloc +#define malloc(size) Curl_cmalloc(size) +#undef calloc +#define calloc(nbelem,size) Curl_ccalloc(nbelem, size) +#undef realloc +#define realloc(ptr,size) Curl_crealloc(ptr, size) +#undef free +#define free(ptr) Curl_cfree(ptr) + +#ifdef _WIN32 +# ifdef UNICODE +# undef wcsdup +# define wcsdup(ptr) Curl_cwcsdup(ptr) +# undef _wcsdup +# define _wcsdup(ptr) Curl_cwcsdup(ptr) +# undef _tcsdup +# define _tcsdup(ptr) Curl_cwcsdup(ptr) +# else +# undef _tcsdup +# define _tcsdup(ptr) Curl_cstrdup(ptr) +# endif +#endif + +#endif /* CURLDEBUG */ +#endif /* HEADER_CURL_MEMORY_H */ diff --git a/Utilities/cmcurl/lib/curl_memrchr.c b/Utilities/cmcurl/lib/curl_memrchr.c new file mode 100644 index 0000000..3f3dc6d --- /dev/null +++ b/Utilities/cmcurl/lib/curl_memrchr.c @@ -0,0 +1,64 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "curl_memrchr.h" +#include "curl_memory.h" + +/* The last #include file should be: */ +#include "memdebug.h" + +#ifndef HAVE_MEMRCHR + +/* + * Curl_memrchr() + * + * Our memrchr() function clone for systems which lack this function. The + * memrchr() function is like the memchr() function, except that it searches + * backwards from the end of the n bytes pointed to by s instead of forward + * from the beginning. + */ + +void * +Curl_memrchr(const void *s, int c, size_t n) +{ + if(n > 0) { + const unsigned char *p = s; + const unsigned char *q = s; + + p += n - 1; + + while(p >= q) { + if(*p == (unsigned char)c) + return (void *)p; + p--; + } + } + return NULL; +} + +#endif /* HAVE_MEMRCHR */ diff --git a/Utilities/cmcurl/lib/curl_memrchr.h b/Utilities/cmcurl/lib/curl_memrchr.h new file mode 100644 index 0000000..45bb38c --- /dev/null +++ b/Utilities/cmcurl/lib/curl_memrchr.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_MEMRCHR_H +#define HEADER_CURL_MEMRCHR_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_MEMRCHR + +#include <string.h> +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif + +#else /* HAVE_MEMRCHR */ + +void *Curl_memrchr(const void *s, int c, size_t n); + +#define memrchr(x,y,z) Curl_memrchr((x),(y),(z)) + +#endif /* HAVE_MEMRCHR */ + +#endif /* HEADER_CURL_MEMRCHR_H */ diff --git a/Utilities/cmcurl/lib/curl_multibyte.c b/Utilities/cmcurl/lib/curl_multibyte.c new file mode 100644 index 0000000..ff21098 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_multibyte.c @@ -0,0 +1,179 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * This file is 'mem-include-scan' clean, which means memdebug.h and + * curl_memory.h are purposely not included in this file. See test 1132. + * + * The functions in this file are curlx functions which are not tracked by the + * curl memory tracker memdebug. + */ + +#include "curl_setup.h" + +#if defined(_WIN32) + +#include "curl_multibyte.h" + +/* + * MultiByte conversions using Windows kernel32 library. + */ + +wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8) +{ + wchar_t *str_w = NULL; + + if(str_utf8) { + int str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + str_utf8, -1, NULL, 0); + if(str_w_len > 0) { + str_w = malloc(str_w_len * sizeof(wchar_t)); + if(str_w) { + if(MultiByteToWideChar(CP_UTF8, 0, str_utf8, -1, str_w, + str_w_len) == 0) { + free(str_w); + return NULL; + } + } + } + } + + return str_w; +} + +char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w) +{ + char *str_utf8 = NULL; + + if(str_w) { + int bytes = WideCharToMultiByte(CP_UTF8, 0, str_w, -1, + NULL, 0, NULL, NULL); + if(bytes > 0) { + str_utf8 = malloc(bytes); + if(str_utf8) { + if(WideCharToMultiByte(CP_UTF8, 0, str_w, -1, str_utf8, bytes, + NULL, NULL) == 0) { + free(str_utf8); + return NULL; + } + } + } + } + + return str_utf8; +} + +#endif /* _WIN32 */ + +#if defined(USE_WIN32_LARGE_FILES) || defined(USE_WIN32_SMALL_FILES) + +int curlx_win32_open(const char *filename, int oflag, ...) +{ + int pmode = 0; + +#ifdef _UNICODE + int result = -1; + wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename); +#endif + + va_list param; + va_start(param, oflag); + if(oflag & O_CREAT) + pmode = va_arg(param, int); + va_end(param); + +#ifdef _UNICODE + if(filename_w) { + result = _wopen(filename_w, oflag, pmode); + curlx_unicodefree(filename_w); + } + else + errno = EINVAL; + return result; +#else + return (_open)(filename, oflag, pmode); +#endif +} + +FILE *curlx_win32_fopen(const char *filename, const char *mode) +{ +#ifdef _UNICODE + FILE *result = NULL; + wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename); + wchar_t *mode_w = curlx_convert_UTF8_to_wchar(mode); + if(filename_w && mode_w) + result = _wfopen(filename_w, mode_w); + else + errno = EINVAL; + curlx_unicodefree(filename_w); + curlx_unicodefree(mode_w); + return result; +#else + return (fopen)(filename, mode); +#endif +} + +int curlx_win32_stat(const char *path, struct_stat *buffer) +{ +#ifdef _UNICODE + int result = -1; + wchar_t *path_w = curlx_convert_UTF8_to_wchar(path); + if(path_w) { +#if defined(USE_WIN32_SMALL_FILES) + result = _wstat(path_w, buffer); +#else + result = _wstati64(path_w, buffer); +#endif + curlx_unicodefree(path_w); + } + else + errno = EINVAL; + return result; +#else +#if defined(USE_WIN32_SMALL_FILES) + return _stat(path, buffer); +#else + return _stati64(path, buffer); +#endif +#endif +} + +int curlx_win32_access(const char *path, int mode) +{ +#if defined(_UNICODE) + int result = -1; + wchar_t *path_w = curlx_convert_UTF8_to_wchar(path); + if(path_w) { + result = _waccess(path_w, mode); + curlx_unicodefree(path_w); + } + else + errno = EINVAL; + return result; +#else + return _access(path, mode); +#endif +} + +#endif /* USE_WIN32_LARGE_FILES || USE_WIN32_SMALL_FILES */ diff --git a/Utilities/cmcurl/lib/curl_multibyte.h b/Utilities/cmcurl/lib/curl_multibyte.h new file mode 100644 index 0000000..8b9ac71 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_multibyte.h @@ -0,0 +1,91 @@ +#ifndef HEADER_CURL_MULTIBYTE_H +#define HEADER_CURL_MULTIBYTE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if defined(_WIN32) + + /* + * MultiByte conversions using Windows kernel32 library. + */ + +wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8); +char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w); +#endif /* _WIN32 */ + +/* + * Macros curlx_convert_UTF8_to_tchar(), curlx_convert_tchar_to_UTF8() + * and curlx_unicodefree() main purpose is to minimize the number of + * preprocessor conditional directives needed by code using these + * to differentiate UNICODE from non-UNICODE builds. + * + * In the case of a non-UNICODE build the tchar strings are char strings that + * are duplicated via strdup and remain in whatever the passed in encoding is, + * which is assumed to be UTF-8 but may be other encoding. Therefore the + * significance of the conversion functions is primarily for UNICODE builds. + * + * Allocated memory should be free'd with curlx_unicodefree(). + * + * Note: Because these are curlx functions their memory usage is not tracked + * by the curl memory tracker memdebug. You'll notice that curlx function-like + * macros call free and strdup in parentheses, eg (strdup)(ptr), and that's to + * ensure that the curl memdebug override macros do not replace them. + */ + +#if defined(UNICODE) && defined(_WIN32) + +#define curlx_convert_UTF8_to_tchar(ptr) curlx_convert_UTF8_to_wchar((ptr)) +#define curlx_convert_tchar_to_UTF8(ptr) curlx_convert_wchar_to_UTF8((ptr)) + +typedef union { + unsigned short *tchar_ptr; + const unsigned short *const_tchar_ptr; + unsigned short *tbyte_ptr; + const unsigned short *const_tbyte_ptr; +} xcharp_u; + +#else + +#define curlx_convert_UTF8_to_tchar(ptr) (strdup)(ptr) +#define curlx_convert_tchar_to_UTF8(ptr) (strdup)(ptr) + +typedef union { + char *tchar_ptr; + const char *const_tchar_ptr; + unsigned char *tbyte_ptr; + const unsigned char *const_tbyte_ptr; +} xcharp_u; + +#endif /* UNICODE && _WIN32 */ + +#define curlx_unicodefree(ptr) \ + do { \ + if(ptr) { \ + (free)(ptr); \ + (ptr) = NULL; \ + } \ + } while(0) + +#endif /* HEADER_CURL_MULTIBYTE_H */ diff --git a/Utilities/cmcurl/lib/curl_ntlm_core.c b/Utilities/cmcurl/lib/curl_ntlm_core.c new file mode 100644 index 0000000..6f6d75c --- /dev/null +++ b/Utilities/cmcurl/lib/curl_ntlm_core.c @@ -0,0 +1,669 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_CURL_NTLM_CORE) + +/* + * NTLM details: + * + * https://davenport.sourceforge.net/ntlm.html + * https://www.innovation.ch/java/ntlm.html + */ + +/* Please keep the SSL backend-specific #if branches in this order: + + 1. USE_OPENSSL + 2. USE_WOLFSSL + 3. USE_GNUTLS + 4. - + 5. USE_MBEDTLS + 6. USE_SECTRANSP + 7. USE_OS400CRYPTO + 8. USE_WIN32_CRYPTO + + This ensures that: + - the same SSL branch gets activated throughout this source + file even if multiple backends are enabled at the same time. + - OpenSSL has higher priority than Windows Crypt, due + to issues with the latter supporting NTLM2Session responses + in NTLM type-3 messages. + */ + +#if defined(USE_OPENSSL) + #include <openssl/opensslconf.h> + #if !defined(OPENSSL_NO_DES) && !defined(OPENSSL_NO_DEPRECATED_3_0) + #define USE_OPENSSL_DES + #endif +#endif + +#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) + +#if defined(USE_OPENSSL) +# include <openssl/des.h> +# include <openssl/md5.h> +# include <openssl/ssl.h> +# include <openssl/rand.h> +#else +# include <wolfssl/options.h> +# include <wolfssl/openssl/des.h> +# include <wolfssl/openssl/md5.h> +# include <wolfssl/openssl/ssl.h> +# include <wolfssl/openssl/rand.h> +#endif + +# if (defined(OPENSSL_VERSION_NUMBER) && \ + (OPENSSL_VERSION_NUMBER < 0x00907001L)) && !defined(USE_WOLFSSL) +# define DES_key_schedule des_key_schedule +# define DES_cblock des_cblock +# define DES_set_odd_parity des_set_odd_parity +# define DES_set_key des_set_key +# define DES_ecb_encrypt des_ecb_encrypt +# define DESKEY(x) x +# define DESKEYARG(x) x +# elif defined(OPENSSL_IS_AWSLC) +# define DES_set_key_unchecked (void)DES_set_key +# define DESKEYARG(x) *x +# define DESKEY(x) &x +# else +# define DESKEYARG(x) *x +# define DESKEY(x) &x +# endif + +#elif defined(USE_GNUTLS) + +# include <nettle/des.h> + +#elif defined(USE_MBEDTLS) + +# include <mbedtls/des.h> + +#elif defined(USE_SECTRANSP) + +# include <CommonCrypto/CommonCryptor.h> +# include <CommonCrypto/CommonDigest.h> + +#elif defined(USE_OS400CRYPTO) +# include "cipher.mih" /* mih/cipher */ +#elif defined(USE_WIN32_CRYPTO) +# include <wincrypt.h> +#else +# error "Can't compile NTLM support without a crypto library with DES." +# define CURL_NTLM_NOT_SUPPORTED +#endif + +#include "urldata.h" +#include "strcase.h" +#include "curl_ntlm_core.h" +#include "curl_md5.h" +#include "curl_hmac.h" +#include "warnless.h" +#include "curl_endian.h" +#include "curl_des.h" +#include "curl_md4.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define NTLMv2_BLOB_SIGNATURE "\x01\x01\x00\x00" +#define NTLMv2_BLOB_LEN (44 -16 + ntlm->target_info_len + 4) + +#if !defined(CURL_NTLM_NOT_SUPPORTED) +/* +* Turns a 56-bit key into being 64-bit wide. +*/ +static void extend_key_56_to_64(const unsigned char *key_56, char *key) +{ + key[0] = key_56[0]; + key[1] = (unsigned char)(((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1)); + key[2] = (unsigned char)(((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2)); + key[3] = (unsigned char)(((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3)); + key[4] = (unsigned char)(((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4)); + key[5] = (unsigned char)(((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5)); + key[6] = (unsigned char)(((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6)); + key[7] = (unsigned char) ((key_56[6] << 1) & 0xFF); +} +#endif + +#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) +/* + * Turns a 56 bit key into the 64 bit, odd parity key and sets the key. The + * key schedule ks is also set. + */ +static void setup_des_key(const unsigned char *key_56, + DES_key_schedule DESKEYARG(ks)) +{ + DES_cblock key; + + /* Expand the 56-bit key to 64-bits */ + extend_key_56_to_64(key_56, (char *) &key); + + /* Set the key parity to odd */ + DES_set_odd_parity(&key); + + /* Set the key */ + DES_set_key_unchecked(&key, ks); +} + +#elif defined(USE_GNUTLS) + +static void setup_des_key(const unsigned char *key_56, + struct des_ctx *des) +{ + char key[8]; + + /* Expand the 56-bit key to 64-bits */ + extend_key_56_to_64(key_56, key); + + /* Set the key parity to odd */ + Curl_des_set_odd_parity((unsigned char *) key, sizeof(key)); + + /* Set the key */ + des_set_key(des, (const uint8_t *) key); +} + +#elif defined(USE_MBEDTLS) + +static bool encrypt_des(const unsigned char *in, unsigned char *out, + const unsigned char *key_56) +{ + mbedtls_des_context ctx; + char key[8]; + + /* Expand the 56-bit key to 64-bits */ + extend_key_56_to_64(key_56, key); + + /* Set the key parity to odd */ + mbedtls_des_key_set_parity((unsigned char *) key); + + /* Perform the encryption */ + mbedtls_des_init(&ctx); + mbedtls_des_setkey_enc(&ctx, (unsigned char *) key); + return mbedtls_des_crypt_ecb(&ctx, in, out) == 0; +} + +#elif defined(USE_SECTRANSP) + +static bool encrypt_des(const unsigned char *in, unsigned char *out, + const unsigned char *key_56) +{ + char key[8]; + size_t out_len; + CCCryptorStatus err; + + /* Expand the 56-bit key to 64-bits */ + extend_key_56_to_64(key_56, key); + + /* Set the key parity to odd */ + Curl_des_set_odd_parity((unsigned char *) key, sizeof(key)); + + /* Perform the encryption */ + err = CCCrypt(kCCEncrypt, kCCAlgorithmDES, kCCOptionECBMode, key, + kCCKeySizeDES, NULL, in, 8 /* inbuflen */, out, + 8 /* outbuflen */, &out_len); + + return err == kCCSuccess; +} + +#elif defined(USE_OS400CRYPTO) + +static bool encrypt_des(const unsigned char *in, unsigned char *out, + const unsigned char *key_56) +{ + char key[8]; + _CIPHER_Control_T ctl; + + /* Setup the cipher control structure */ + ctl.Func_ID = ENCRYPT_ONLY; + ctl.Data_Len = sizeof(key); + + /* Expand the 56-bit key to 64-bits */ + extend_key_56_to_64(key_56, ctl.Crypto_Key); + + /* Set the key parity to odd */ + Curl_des_set_odd_parity((unsigned char *) ctl.Crypto_Key, ctl.Data_Len); + + /* Perform the encryption */ + _CIPHER((_SPCPTR *) &out, &ctl, (_SPCPTR *) &in); + + return TRUE; +} + +#elif defined(USE_WIN32_CRYPTO) + +static bool encrypt_des(const unsigned char *in, unsigned char *out, + const unsigned char *key_56) +{ + HCRYPTPROV hprov; + HCRYPTKEY hkey; + struct { + BLOBHEADER hdr; + unsigned int len; + char key[8]; + } blob; + DWORD len = 8; + + /* Acquire the crypto provider */ + if(!CryptAcquireContext(&hprov, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + return FALSE; + + /* Setup the key blob structure */ + memset(&blob, 0, sizeof(blob)); + blob.hdr.bType = PLAINTEXTKEYBLOB; + blob.hdr.bVersion = 2; + blob.hdr.aiKeyAlg = CALG_DES; + blob.len = sizeof(blob.key); + + /* Expand the 56-bit key to 64-bits */ + extend_key_56_to_64(key_56, blob.key); + + /* Set the key parity to odd */ + Curl_des_set_odd_parity((unsigned char *) blob.key, sizeof(blob.key)); + + /* Import the key */ + if(!CryptImportKey(hprov, (BYTE *) &blob, sizeof(blob), 0, 0, &hkey)) { + CryptReleaseContext(hprov, 0); + + return FALSE; + } + + memcpy(out, in, 8); + + /* Perform the encryption */ + CryptEncrypt(hkey, 0, FALSE, 0, out, &len, len); + + CryptDestroyKey(hkey); + CryptReleaseContext(hprov, 0); + + return TRUE; +} + +#endif /* defined(USE_WIN32_CRYPTO) */ + + /* + * takes a 21 byte array and treats it as 3 56-bit DES keys. The + * 8 byte plaintext is encrypted with each key and the resulting 24 + * bytes are stored in the results array. + */ +void Curl_ntlm_core_lm_resp(const unsigned char *keys, + const unsigned char *plaintext, + unsigned char *results) +{ +#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) + DES_key_schedule ks; + + setup_des_key(keys, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) results, + DESKEY(ks), DES_ENCRYPT); + + setup_des_key(keys + 7, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results + 8), + DESKEY(ks), DES_ENCRYPT); + + setup_des_key(keys + 14, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results + 16), + DESKEY(ks), DES_ENCRYPT); +#elif defined(USE_GNUTLS) + struct des_ctx des; + setup_des_key(keys, &des); + des_encrypt(&des, 8, results, plaintext); + setup_des_key(keys + 7, &des); + des_encrypt(&des, 8, results + 8, plaintext); + setup_des_key(keys + 14, &des); + des_encrypt(&des, 8, results + 16, plaintext); +#elif defined(USE_MBEDTLS) || defined(USE_SECTRANSP) \ + || defined(USE_OS400CRYPTO) || defined(USE_WIN32_CRYPTO) + encrypt_des(plaintext, results, keys); + encrypt_des(plaintext, results + 8, keys + 7); + encrypt_des(plaintext, results + 16, keys + 14); +#else + (void)keys; + (void)plaintext; + (void)results; +#endif +} + +/* + * Set up lanmanager hashed password + */ +CURLcode Curl_ntlm_core_mk_lm_hash(const char *password, + unsigned char *lmbuffer /* 21 bytes */) +{ + unsigned char pw[14]; +#if !defined(CURL_NTLM_NOT_SUPPORTED) + static const unsigned char magic[] = { + 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 /* i.e. KGS!@#$% */ + }; +#endif + size_t len = CURLMIN(strlen(password), 14); + + Curl_strntoupper((char *)pw, password, len); + memset(&pw[len], 0, 14 - len); + + { + /* Create LanManager hashed password. */ + +#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) + DES_key_schedule ks; + + setup_des_key(pw, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock *)magic, (DES_cblock *)lmbuffer, + DESKEY(ks), DES_ENCRYPT); + + setup_des_key(pw + 7, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock *)magic, (DES_cblock *)(lmbuffer + 8), + DESKEY(ks), DES_ENCRYPT); +#elif defined(USE_GNUTLS) + struct des_ctx des; + setup_des_key(pw, &des); + des_encrypt(&des, 8, lmbuffer, magic); + setup_des_key(pw + 7, &des); + des_encrypt(&des, 8, lmbuffer + 8, magic); +#elif defined(USE_MBEDTLS) || defined(USE_SECTRANSP) \ + || defined(USE_OS400CRYPTO) || defined(USE_WIN32_CRYPTO) + encrypt_des(magic, lmbuffer, pw); + encrypt_des(magic, lmbuffer + 8, pw + 7); +#endif + + memset(lmbuffer + 16, 0, 21 - 16); + } + + return CURLE_OK; +} + +static void ascii_to_unicode_le(unsigned char *dest, const char *src, + size_t srclen) +{ + size_t i; + for(i = 0; i < srclen; i++) { + dest[2 * i] = (unsigned char)src[i]; + dest[2 * i + 1] = '\0'; + } +} + +#if !defined(USE_WINDOWS_SSPI) + +static void ascii_uppercase_to_unicode_le(unsigned char *dest, + const char *src, size_t srclen) +{ + size_t i; + for(i = 0; i < srclen; i++) { + dest[2 * i] = (unsigned char)(Curl_raw_toupper(src[i])); + dest[2 * i + 1] = '\0'; + } +} + +#endif /* !USE_WINDOWS_SSPI */ + +/* + * Set up nt hashed passwords + * @unittest: 1600 + */ +CURLcode Curl_ntlm_core_mk_nt_hash(const char *password, + unsigned char *ntbuffer /* 21 bytes */) +{ + size_t len = strlen(password); + unsigned char *pw; + CURLcode result; + if(len > SIZE_T_MAX/2) /* avoid integer overflow */ + return CURLE_OUT_OF_MEMORY; + pw = len ? malloc(len * 2) : (unsigned char *)strdup(""); + if(!pw) + return CURLE_OUT_OF_MEMORY; + + ascii_to_unicode_le(pw, password, len); + + /* Create NT hashed password. */ + result = Curl_md4it(ntbuffer, pw, 2 * len); + if(!result) + memset(ntbuffer + 16, 0, 21 - 16); + + free(pw); + + return result; +} + +#if !defined(USE_WINDOWS_SSPI) + +/* Timestamp in tenths of a microsecond since January 1, 1601 00:00:00 UTC. */ +struct ms_filetime { + unsigned int dwLowDateTime; + unsigned int dwHighDateTime; +}; + +/* Convert a time_t to an MS FILETIME (MS-DTYP section 2.3.3). */ +static void time2filetime(struct ms_filetime *ft, time_t t) +{ +#if SIZEOF_TIME_T > 4 + t = (t + CURL_OFF_T_C(11644473600)) * 10000000; + ft->dwLowDateTime = (unsigned int) (t & 0xFFFFFFFF); + ft->dwHighDateTime = (unsigned int) (t >> 32); +#else + unsigned int r, s; + unsigned int i; + + ft->dwLowDateTime = t & 0xFFFFFFFF; + ft->dwHighDateTime = 0; + +# ifndef HAVE_TIME_T_UNSIGNED + /* Extend sign if needed. */ + if(ft->dwLowDateTime & 0x80000000) + ft->dwHighDateTime = ~0; +# endif + + /* Bias seconds to Jan 1, 1601. + 134774 days = 11644473600 seconds = 0x2B6109100 */ + r = ft->dwLowDateTime; + ft->dwLowDateTime = (ft->dwLowDateTime + 0xB6109100U) & 0xFFFFFFFF; + ft->dwHighDateTime += ft->dwLowDateTime < r? 0x03: 0x02; + + /* Convert to tenths of microseconds. */ + ft->dwHighDateTime *= 10000000; + i = 32; + do { + i -= 8; + s = ((ft->dwLowDateTime >> i) & 0xFF) * (10000000 - 1); + r = (s << i) & 0xFFFFFFFF; + s >>= 1; /* Split shift to avoid width overflow. */ + s >>= 31 - i; + ft->dwLowDateTime = (ft->dwLowDateTime + r) & 0xFFFFFFFF; + if(ft->dwLowDateTime < r) + s++; + ft->dwHighDateTime += s; + } while(i); + ft->dwHighDateTime &= 0xFFFFFFFF; +#endif +} + +/* This creates the NTLMv2 hash by using NTLM hash as the key and Unicode + * (uppercase UserName + Domain) as the data + */ +CURLcode Curl_ntlm_core_mk_ntlmv2_hash(const char *user, size_t userlen, + const char *domain, size_t domlen, + unsigned char *ntlmhash, + unsigned char *ntlmv2hash) +{ + /* Unicode representation */ + size_t identity_len; + unsigned char *identity; + CURLcode result = CURLE_OK; + + if((userlen > CURL_MAX_INPUT_LENGTH) || (domlen > CURL_MAX_INPUT_LENGTH)) + return CURLE_OUT_OF_MEMORY; + + identity_len = (userlen + domlen) * 2; + identity = malloc(identity_len + 1); + + if(!identity) + return CURLE_OUT_OF_MEMORY; + + ascii_uppercase_to_unicode_le(identity, user, userlen); + ascii_to_unicode_le(identity + (userlen << 1), domain, domlen); + + result = Curl_hmacit(Curl_HMAC_MD5, ntlmhash, 16, identity, identity_len, + ntlmv2hash); + free(identity); + + return result; +} + +/* + * Curl_ntlm_core_mk_ntlmv2_resp() + * + * This creates the NTLMv2 response as set in the ntlm type-3 message. + * + * Parameters: + * + * ntlmv2hash [in] - The ntlmv2 hash (16 bytes) + * challenge_client [in] - The client nonce (8 bytes) + * ntlm [in] - The ntlm data struct being used to read TargetInfo + and Server challenge received in the type-2 message + * ntresp [out] - The address where a pointer to newly allocated + * memory holding the NTLMv2 response. + * ntresp_len [out] - The length of the output message. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_ntlm_core_mk_ntlmv2_resp(unsigned char *ntlmv2hash, + unsigned char *challenge_client, + struct ntlmdata *ntlm, + unsigned char **ntresp, + unsigned int *ntresp_len) +{ +/* NTLMv2 response structure : +------------------------------------------------------------------------------ +0 HMAC MD5 16 bytes +------BLOB-------------------------------------------------------------------- +16 Signature 0x01010000 +20 Reserved long (0x00000000) +24 Timestamp LE, 64-bit signed value representing the number of + tenths of a microsecond since January 1, 1601. +32 Client Nonce 8 bytes +40 Unknown 4 bytes +44 Target Info N bytes (from the type-2 message) +44+N Unknown 4 bytes +------------------------------------------------------------------------------ +*/ + + unsigned int len = 0; + unsigned char *ptr = NULL; + unsigned char hmac_output[HMAC_MD5_LENGTH]; + struct ms_filetime tw; + + CURLcode result = CURLE_OK; + + /* Calculate the timestamp */ +#ifdef DEBUGBUILD + char *force_timestamp = getenv("CURL_FORCETIME"); + if(force_timestamp) + time2filetime(&tw, (time_t) 0); + else +#endif + time2filetime(&tw, time(NULL)); + + /* Calculate the response len */ + len = HMAC_MD5_LENGTH + NTLMv2_BLOB_LEN; + + /* Allocate the response */ + ptr = calloc(1, len); + if(!ptr) + return CURLE_OUT_OF_MEMORY; + + /* Create the BLOB structure */ + msnprintf((char *)ptr + HMAC_MD5_LENGTH, NTLMv2_BLOB_LEN, + "%c%c%c%c" /* NTLMv2_BLOB_SIGNATURE */ + "%c%c%c%c" /* Reserved = 0 */ + "%c%c%c%c%c%c%c%c", /* Timestamp */ + NTLMv2_BLOB_SIGNATURE[0], NTLMv2_BLOB_SIGNATURE[1], + NTLMv2_BLOB_SIGNATURE[2], NTLMv2_BLOB_SIGNATURE[3], + 0, 0, 0, 0, + LONGQUARTET(tw.dwLowDateTime), LONGQUARTET(tw.dwHighDateTime)); + + memcpy(ptr + 32, challenge_client, 8); + if(ntlm->target_info_len) + memcpy(ptr + 44, ntlm->target_info, ntlm->target_info_len); + + /* Concatenate the Type 2 challenge with the BLOB and do HMAC MD5 */ + memcpy(ptr + 8, &ntlm->nonce[0], 8); + result = Curl_hmacit(Curl_HMAC_MD5, ntlmv2hash, HMAC_MD5_LENGTH, ptr + 8, + NTLMv2_BLOB_LEN + 8, hmac_output); + if(result) { + free(ptr); + return result; + } + + /* Concatenate the HMAC MD5 output with the BLOB */ + memcpy(ptr, hmac_output, HMAC_MD5_LENGTH); + + /* Return the response */ + *ntresp = ptr; + *ntresp_len = len; + + return result; +} + +/* + * Curl_ntlm_core_mk_lmv2_resp() + * + * This creates the LMv2 response as used in the ntlm type-3 message. + * + * Parameters: + * + * ntlmv2hash [in] - The ntlmv2 hash (16 bytes) + * challenge_client [in] - The client nonce (8 bytes) + * challenge_client [in] - The server challenge (8 bytes) + * lmresp [out] - The LMv2 response (24 bytes) + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_ntlm_core_mk_lmv2_resp(unsigned char *ntlmv2hash, + unsigned char *challenge_client, + unsigned char *challenge_server, + unsigned char *lmresp) +{ + unsigned char data[16]; + unsigned char hmac_output[16]; + CURLcode result = CURLE_OK; + + memcpy(&data[0], challenge_server, 8); + memcpy(&data[8], challenge_client, 8); + + result = Curl_hmacit(Curl_HMAC_MD5, ntlmv2hash, 16, &data[0], 16, + hmac_output); + if(result) + return result; + + /* Concatenate the HMAC MD5 output with the client nonce */ + memcpy(lmresp, hmac_output, 16); + memcpy(lmresp + 16, challenge_client, 8); + + return result; +} + +#endif /* !USE_WINDOWS_SSPI */ + +#endif /* USE_CURL_NTLM_CORE */ diff --git a/Utilities/cmcurl/lib/curl_ntlm_core.h b/Utilities/cmcurl/lib/curl_ntlm_core.h new file mode 100644 index 0000000..0c62ee0 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_ntlm_core.h @@ -0,0 +1,79 @@ +#ifndef HEADER_CURL_NTLM_CORE_H +#define HEADER_CURL_NTLM_CORE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_CURL_NTLM_CORE) + +#if defined(USE_OPENSSL) +# include <openssl/ssl.h> +#elif defined(USE_WOLFSSL) +# include <wolfssl/options.h> +# include <wolfssl/openssl/ssl.h> +#endif + +/* Helpers to generate function byte arguments in little endian order */ +#define SHORTPAIR(x) ((int)((x) & 0xff)), ((int)(((x) >> 8) & 0xff)) +#define LONGQUARTET(x) ((int)((x) & 0xff)), ((int)(((x) >> 8) & 0xff)), \ + ((int)(((x) >> 16) & 0xff)), ((int)(((x) >> 24) & 0xff)) + +void Curl_ntlm_core_lm_resp(const unsigned char *keys, + const unsigned char *plaintext, + unsigned char *results); + +CURLcode Curl_ntlm_core_mk_lm_hash(const char *password, + unsigned char *lmbuffer /* 21 bytes */); + +CURLcode Curl_ntlm_core_mk_nt_hash(const char *password, + unsigned char *ntbuffer /* 21 bytes */); + +#if !defined(USE_WINDOWS_SSPI) + +CURLcode Curl_hmac_md5(const unsigned char *key, unsigned int keylen, + const unsigned char *data, unsigned int datalen, + unsigned char *output); + +CURLcode Curl_ntlm_core_mk_ntlmv2_hash(const char *user, size_t userlen, + const char *domain, size_t domlen, + unsigned char *ntlmhash, + unsigned char *ntlmv2hash); + +CURLcode Curl_ntlm_core_mk_ntlmv2_resp(unsigned char *ntlmv2hash, + unsigned char *challenge_client, + struct ntlmdata *ntlm, + unsigned char **ntresp, + unsigned int *ntresp_len); + +CURLcode Curl_ntlm_core_mk_lmv2_resp(unsigned char *ntlmv2hash, + unsigned char *challenge_client, + unsigned char *challenge_server, + unsigned char *lmresp); + +#endif /* !USE_WINDOWS_SSPI */ + +#endif /* USE_CURL_NTLM_CORE */ + +#endif /* HEADER_CURL_NTLM_CORE_H */ diff --git a/Utilities/cmcurl/lib/curl_ntlm_wb.c b/Utilities/cmcurl/lib/curl_ntlm_wb.c new file mode 100644 index 0000000..b087a37 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_ntlm_wb.c @@ -0,0 +1,500 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ + defined(NTLM_WB_ENABLED) + +/* + * NTLM details: + * + * https://davenport.sourceforge.net/ntlm.html + * https://www.innovation.ch/java/ntlm.html + */ + +#define DEBUG_ME 0 + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#include <signal.h> +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#include "urldata.h" +#include "sendf.h" +#include "select.h" +#include "vauth/ntlm.h" +#include "curl_ntlm_core.h" +#include "curl_ntlm_wb.h" +#include "url.h" +#include "strerror.h" +#include "strdup.h" +#include "strcase.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#if DEBUG_ME +# define DEBUG_OUT(x) x +#else +# define DEBUG_OUT(x) Curl_nop_stmt +#endif + +/* Portable 'sclose_nolog' used only in child process instead of 'sclose' + to avoid fooling the socket leak detector */ +#ifdef HAVE_PIPE +# define sclose_nolog(x) close((x)) +#elif defined(HAVE_CLOSESOCKET) +# define sclose_nolog(x) closesocket((x)) +#elif defined(HAVE_CLOSESOCKET_CAMEL) +# define sclose_nolog(x) CloseSocket((x)) +#else +# define sclose_nolog(x) close((x)) +#endif + +static void ntlm_wb_cleanup(struct ntlmdata *ntlm) +{ + if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) { + sclose(ntlm->ntlm_auth_hlpr_socket); + ntlm->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; + } + + if(ntlm->ntlm_auth_hlpr_pid) { + int i; + for(i = 0; i < 4; i++) { + pid_t ret = waitpid(ntlm->ntlm_auth_hlpr_pid, NULL, WNOHANG); + if(ret == ntlm->ntlm_auth_hlpr_pid || errno == ECHILD) + break; + switch(i) { + case 0: + kill(ntlm->ntlm_auth_hlpr_pid, SIGTERM); + break; + case 1: + /* Give the process another moment to shut down cleanly before + bringing down the axe */ + Curl_wait_ms(1); + break; + case 2: + kill(ntlm->ntlm_auth_hlpr_pid, SIGKILL); + break; + case 3: + break; + } + } + ntlm->ntlm_auth_hlpr_pid = 0; + } + + Curl_safefree(ntlm->challenge); + Curl_safefree(ntlm->response); +} + +static CURLcode ntlm_wb_init(struct Curl_easy *data, struct ntlmdata *ntlm, + const char *userp) +{ + curl_socket_t sockfds[2]; + pid_t child_pid; + const char *username; + char *slash, *domain = NULL; + const char *ntlm_auth = NULL; + char *ntlm_auth_alloc = NULL; +#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) + struct passwd pw, *pw_res; + char pwbuf[1024]; +#endif + char buffer[STRERROR_LEN]; + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) data; +#endif + + /* Return if communication with ntlm_auth already set up */ + if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD || + ntlm->ntlm_auth_hlpr_pid) + return CURLE_OK; + + username = userp; + /* The real ntlm_auth really doesn't like being invoked with an + empty username. It won't make inferences for itself, and expects + the client to do so (mostly because it's really designed for + servers like squid to use for auth, and client support is an + afterthought for it). So try hard to provide a suitable username + if we don't already have one. But if we can't, provide the + empty one anyway. Perhaps they have an implementation of the + ntlm_auth helper which *doesn't* need it so we might as well try */ + if(!username || !username[0]) { + username = getenv("NTLMUSER"); + if(!username || !username[0]) + username = getenv("LOGNAME"); + if(!username || !username[0]) + username = getenv("USER"); +#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) + if((!username || !username[0]) && + !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) && + pw_res) { + username = pw.pw_name; + } +#endif + if(!username || !username[0]) + username = userp; + } + slash = strpbrk(username, "\\/"); + if(slash) { + domain = strdup(username); + if(!domain) + return CURLE_OUT_OF_MEMORY; + slash = domain + (slash - username); + *slash = '\0'; + username = username + (slash - domain) + 1; + } + + /* For testing purposes, when DEBUGBUILD is defined and environment + variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform + NTLM challenge/response which only accepts commands and output + strings pre-written in test case definitions */ +#ifdef DEBUGBUILD + ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE"); + if(ntlm_auth_alloc) + ntlm_auth = ntlm_auth_alloc; + else +#endif + ntlm_auth = NTLM_WB_FILE; + + if(access(ntlm_auth, X_OK) != 0) { + failf(data, "Could not access ntlm_auth: %s errno %d: %s", + ntlm_auth, errno, Curl_strerror(errno, buffer, sizeof(buffer))); + goto done; + } + + if(wakeup_create(sockfds)) { + failf(data, "Could not open socket pair. errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + goto done; + } + + child_pid = fork(); + if(child_pid == -1) { + wakeup_close(sockfds[0]); + wakeup_close(sockfds[1]); + failf(data, "Could not fork. errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + goto done; + } + else if(!child_pid) { + /* + * child process + */ + + /* Don't use sclose in the child since it fools the socket leak detector */ + sclose_nolog(sockfds[0]); + if(dup2(sockfds[1], STDIN_FILENO) == -1) { + failf(data, "Could not redirect child stdin. errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + exit(1); + } + + if(dup2(sockfds[1], STDOUT_FILENO) == -1) { + failf(data, "Could not redirect child stdout. errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + exit(1); + } + + if(domain) + execl(ntlm_auth, ntlm_auth, + "--helper-protocol", "ntlmssp-client-1", + "--use-cached-creds", + "--username", username, + "--domain", domain, + NULL); + else + execl(ntlm_auth, ntlm_auth, + "--helper-protocol", "ntlmssp-client-1", + "--use-cached-creds", + "--username", username, + NULL); + + sclose_nolog(sockfds[1]); + failf(data, "Could not execl(). errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + exit(1); + } + + sclose(sockfds[1]); + ntlm->ntlm_auth_hlpr_socket = sockfds[0]; + ntlm->ntlm_auth_hlpr_pid = child_pid; + free(domain); + free(ntlm_auth_alloc); + return CURLE_OK; + +done: + free(domain); + free(ntlm_auth_alloc); + return CURLE_REMOTE_ACCESS_DENIED; +} + +/* if larger than this, something is seriously wrong */ +#define MAX_NTLM_WB_RESPONSE 100000 + +static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm, + const char *input, curlntlm state) +{ + size_t len_in = strlen(input), len_out = 0; + struct dynbuf b; + char *ptr = NULL; + unsigned char *buf = (unsigned char *)data->state.buffer; + Curl_dyn_init(&b, MAX_NTLM_WB_RESPONSE); + + while(len_in > 0) { + ssize_t written = wakeup_write(ntlm->ntlm_auth_hlpr_socket, input, len_in); + if(written == -1) { + /* Interrupted by a signal, retry it */ + if(errno == EINTR) + continue; + /* write failed if other errors happen */ + goto done; + } + input += written; + len_in -= written; + } + /* Read one line */ + while(1) { + ssize_t size = + wakeup_read(ntlm->ntlm_auth_hlpr_socket, buf, data->set.buffer_size); + if(size == -1) { + if(errno == EINTR) + continue; + goto done; + } + else if(size == 0) + goto done; + + if(Curl_dyn_addn(&b, buf, size)) + goto done; + + len_out = Curl_dyn_len(&b); + ptr = Curl_dyn_ptr(&b); + if(len_out && ptr[len_out - 1] == '\n') { + ptr[len_out - 1] = '\0'; + break; /* done! */ + } + /* loop */ + } + + /* Samba/winbind installed but not configured */ + if(state == NTLMSTATE_TYPE1 && + len_out == 3 && + ptr[0] == 'P' && ptr[1] == 'W') + goto done; + /* invalid response */ + if(len_out < 4) + goto done; + if(state == NTLMSTATE_TYPE1 && + (ptr[0]!='Y' || ptr[1]!='R' || ptr[2]!=' ')) + goto done; + if(state == NTLMSTATE_TYPE2 && + (ptr[0]!='K' || ptr[1]!='K' || ptr[2]!=' ') && + (ptr[0]!='A' || ptr[1]!='F' || ptr[2]!=' ')) + goto done; + + ntlm->response = strdup(ptr + 3); + Curl_dyn_free(&b); + if(!ntlm->response) + return CURLE_OUT_OF_MEMORY; + return CURLE_OK; +done: + Curl_dyn_free(&b); + return CURLE_REMOTE_ACCESS_DENIED; +} + +CURLcode Curl_input_ntlm_wb(struct Curl_easy *data, + struct connectdata *conn, + bool proxy, + const char *header) +{ + struct ntlmdata *ntlm = proxy ? &conn->proxyntlm : &conn->ntlm; + curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state; + + (void) data; /* In case it gets unused by nop log macros. */ + + if(!checkprefix("NTLM", header)) + return CURLE_BAD_CONTENT_ENCODING; + + header += strlen("NTLM"); + while(*header && ISSPACE(*header)) + header++; + + if(*header) { + ntlm->challenge = strdup(header); + if(!ntlm->challenge) + return CURLE_OUT_OF_MEMORY; + + *state = NTLMSTATE_TYPE2; /* We got a type-2 message */ + } + else { + if(*state == NTLMSTATE_LAST) { + infof(data, "NTLM auth restarted"); + Curl_http_auth_cleanup_ntlm_wb(conn); + } + else if(*state == NTLMSTATE_TYPE3) { + infof(data, "NTLM handshake rejected"); + Curl_http_auth_cleanup_ntlm_wb(conn); + *state = NTLMSTATE_NONE; + return CURLE_REMOTE_ACCESS_DENIED; + } + else if(*state >= NTLMSTATE_TYPE1) { + infof(data, "NTLM handshake failure (internal error)"); + return CURLE_REMOTE_ACCESS_DENIED; + } + + *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */ + } + + return CURLE_OK; +} + +/* + * This is for creating ntlm header output by delegating challenge/response + * to Samba's winbind daemon helper ntlm_auth. + */ +CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn, + bool proxy) +{ + /* point to the address of the pointer that holds the string to send to the + server, which is for a plain host or for an HTTP proxy */ + char **allocuserpwd; + /* point to the name and password for this */ + const char *userp; + struct ntlmdata *ntlm; + curlntlm *state; + struct auth *authp; + + CURLcode res = CURLE_OK; + + DEBUGASSERT(conn); + DEBUGASSERT(data); + + if(proxy) { +#ifndef CURL_DISABLE_PROXY + allocuserpwd = &data->state.aptr.proxyuserpwd; + userp = conn->http_proxy.user; + ntlm = &conn->proxyntlm; + state = &conn->proxy_ntlm_state; + authp = &data->state.authproxy; +#else + return CURLE_NOT_BUILT_IN; +#endif + } + else { + allocuserpwd = &data->state.aptr.userpwd; + userp = conn->user; + ntlm = &conn->ntlm; + state = &conn->http_ntlm_state; + authp = &data->state.authhost; + } + authp->done = FALSE; + + /* not set means empty */ + if(!userp) + userp = ""; + + switch(*state) { + case NTLMSTATE_TYPE1: + default: + /* Use Samba's 'winbind' daemon to support NTLM authentication, + * by delegating the NTLM challenge/response protocol to a helper + * in ntlm_auth. + * https://web.archive.org/web/20190925164737 + * /devel.squid-cache.org/ntlm/squid_helper_protocol.html + * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html + * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html + * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this + * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute + * filename of ntlm_auth helper. + * If NTLM authentication using winbind fails, go back to original + * request handling process. + */ + /* Create communication with ntlm_auth */ + res = ntlm_wb_init(data, ntlm, userp); + if(res) + return res; + res = ntlm_wb_response(data, ntlm, "YR\n", *state); + if(res) + return res; + + free(*allocuserpwd); + *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", + proxy ? "Proxy-" : "", + ntlm->response); + DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd)); + Curl_safefree(ntlm->response); + if(!*allocuserpwd) + return CURLE_OUT_OF_MEMORY; + break; + + case NTLMSTATE_TYPE2: { + char *input = aprintf("TT %s\n", ntlm->challenge); + if(!input) + return CURLE_OUT_OF_MEMORY; + res = ntlm_wb_response(data, ntlm, input, *state); + free(input); + if(res) + return res; + + free(*allocuserpwd); + *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", + proxy ? "Proxy-" : "", + ntlm->response); + DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd)); + *state = NTLMSTATE_TYPE3; /* we sent a type-3 */ + authp->done = TRUE; + Curl_http_auth_cleanup_ntlm_wb(conn); + if(!*allocuserpwd) + return CURLE_OUT_OF_MEMORY; + break; + } + case NTLMSTATE_TYPE3: + /* connection is already authenticated, + * don't send a header in future requests */ + *state = NTLMSTATE_LAST; + /* FALLTHROUGH */ + case NTLMSTATE_LAST: + Curl_safefree(*allocuserpwd); + authp->done = TRUE; + break; + } + + return CURLE_OK; +} + +void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn) +{ + ntlm_wb_cleanup(&conn->ntlm); + ntlm_wb_cleanup(&conn->proxyntlm); +} + +#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */ diff --git a/Utilities/cmcurl/lib/curl_ntlm_wb.h b/Utilities/cmcurl/lib/curl_ntlm_wb.h new file mode 100644 index 0000000..37704c0 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_ntlm_wb.h @@ -0,0 +1,45 @@ +#ifndef HEADER_CURL_NTLM_WB_H +#define HEADER_CURL_NTLM_WB_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ + defined(NTLM_WB_ENABLED) + +/* this is for ntlm header input */ +CURLcode Curl_input_ntlm_wb(struct Curl_easy *data, + struct connectdata *conn, bool proxy, + const char *header); + +/* this is for creating ntlm header output */ +CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn, + bool proxy); + +void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn); + +#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */ + +#endif /* HEADER_CURL_NTLM_WB_H */ diff --git a/Utilities/cmcurl/lib/curl_path.c b/Utilities/cmcurl/lib/curl_path.c new file mode 100644 index 0000000..856423d --- /dev/null +++ b/Utilities/cmcurl/lib/curl_path.c @@ -0,0 +1,199 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl AND ISC + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_SSH) + +#include <curl/curl.h> +#include "curl_memory.h" +#include "curl_path.h" +#include "escape.h" +#include "memdebug.h" + +#define MAX_SSHPATH_LEN 100000 /* arbitrary */ + +/* figure out the path to work with in this particular request */ +CURLcode Curl_getworkingpath(struct Curl_easy *data, + char *homedir, /* when SFTP is used */ + char **path) /* returns the allocated + real path to work with */ +{ + char *working_path; + size_t working_path_len; + struct dynbuf npath; + CURLcode result = + Curl_urldecode(data->state.up.path, 0, &working_path, + &working_path_len, REJECT_ZERO); + if(result) + return result; + + /* new path to switch to in case we need to */ + Curl_dyn_init(&npath, MAX_SSHPATH_LEN); + + /* Check for /~/, indicating relative to the user's home directory */ + if((data->conn->handler->protocol & CURLPROTO_SCP) && + (working_path_len > 3) && (!memcmp(working_path, "/~/", 3))) { + /* It is referenced to the home directory, so strip the leading '/~/' */ + if(Curl_dyn_addn(&npath, &working_path[3], working_path_len - 3)) { + free(working_path); + return CURLE_OUT_OF_MEMORY; + } + } + else if((data->conn->handler->protocol & CURLPROTO_SFTP) && + (!strcmp("/~", working_path) || + ((working_path_len > 2) && !memcmp(working_path, "/~/", 3)))) { + if(Curl_dyn_add(&npath, homedir)) { + free(working_path); + return CURLE_OUT_OF_MEMORY; + } + if(working_path_len > 2) { + size_t len; + const char *p; + int copyfrom = 3; + /* Copy a separating '/' if homedir does not end with one */ + len = Curl_dyn_len(&npath); + p = Curl_dyn_ptr(&npath); + if(len && (p[len-1] != '/')) + copyfrom = 2; + + if(Curl_dyn_addn(&npath, + &working_path[copyfrom], working_path_len - copyfrom)) { + free(working_path); + return CURLE_OUT_OF_MEMORY; + } + } + } + + if(Curl_dyn_len(&npath)) { + free(working_path); + + /* store the pointer for the caller to receive */ + *path = Curl_dyn_ptr(&npath); + } + else + *path = working_path; + + return CURLE_OK; +} + +/* The get_pathname() function is being borrowed from OpenSSH sftp.c + version 4.6p1. */ +/* + * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir) +{ + const char *cp = *cpp, *end; + char quot; + unsigned int i, j; + size_t fullPathLength, pathLength; + bool relativePath = false; + static const char WHITESPACE[] = " \t\r\n"; + + DEBUGASSERT(homedir); + if(!*cp || !homedir) { + *cpp = NULL; + *path = NULL; + return CURLE_QUOTE_ERROR; + } + /* Ignore leading whitespace */ + cp += strspn(cp, WHITESPACE); + /* Allocate enough space for home directory and filename + separator */ + fullPathLength = strlen(cp) + strlen(homedir) + 2; + *path = malloc(fullPathLength); + if(!*path) + return CURLE_OUT_OF_MEMORY; + + /* Check for quoted filenames */ + if(*cp == '\"' || *cp == '\'') { + quot = *cp++; + + /* Search for terminating quote, unescape some chars */ + for(i = j = 0; i <= strlen(cp); i++) { + if(cp[i] == quot) { /* Found quote */ + i++; + (*path)[j] = '\0'; + break; + } + if(cp[i] == '\0') { /* End of string */ + goto fail; + } + if(cp[i] == '\\') { /* Escaped characters */ + i++; + if(cp[i] != '\'' && cp[i] != '\"' && + cp[i] != '\\') { + goto fail; + } + } + (*path)[j++] = cp[i]; + } + + if(j == 0) { + goto fail; + } + *cpp = cp + i + strspn(cp + i, WHITESPACE); + } + else { + /* Read to end of filename - either to whitespace or terminator */ + end = strpbrk(cp, WHITESPACE); + if(!end) + end = strchr(cp, '\0'); + /* return pointer to second parameter if it exists */ + *cpp = end + strspn(end, WHITESPACE); + pathLength = 0; + relativePath = (cp[0] == '/' && cp[1] == '~' && cp[2] == '/'); + /* Handling for relative path - prepend home directory */ + if(relativePath) { + strcpy(*path, homedir); + pathLength = strlen(homedir); + (*path)[pathLength++] = '/'; + (*path)[pathLength] = '\0'; + cp += 3; + } + /* Copy path name up until first "whitespace" */ + memcpy(&(*path)[pathLength], cp, (int)(end - cp)); + pathLength += (int)(end - cp); + (*path)[pathLength] = '\0'; + } + return CURLE_OK; + +fail: + Curl_safefree(*path); + return CURLE_QUOTE_ERROR; +} + +#endif /* if SSH is used */ diff --git a/Utilities/cmcurl/lib/curl_path.h b/Utilities/cmcurl/lib/curl_path.h new file mode 100644 index 0000000..cbe51c2 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_path.h @@ -0,0 +1,49 @@ +#ifndef HEADER_CURL_PATH_H +#define HEADER_CURL_PATH_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include <curl/curl.h> +#include "urldata.h" + +#ifdef _WIN32 +# undef PATH_MAX +# define PATH_MAX MAX_PATH +# ifndef R_OK +# define R_OK 4 +# endif +#endif + +#ifndef PATH_MAX +#define PATH_MAX 1024 /* just an extra precaution since there are systems that + have their definition hidden well */ +#endif + +CURLcode Curl_getworkingpath(struct Curl_easy *data, + char *homedir, + char **path); + +CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir); +#endif /* HEADER_CURL_PATH_H */ diff --git a/Utilities/cmcurl/lib/curl_printf.h b/Utilities/cmcurl/lib/curl_printf.h new file mode 100644 index 0000000..46ef344 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_printf.h @@ -0,0 +1,51 @@ +#ifndef HEADER_CURL_PRINTF_H +#define HEADER_CURL_PRINTF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * This header should be included by ALL code in libcurl that uses any + * *rintf() functions. + */ + +#include <curl/mprintf.h> + +# undef printf +# undef fprintf +# undef msnprintf +# undef vprintf +# undef vfprintf +# undef vsnprintf +# undef mvsnprintf +# undef aprintf +# undef vaprintf +# define printf curl_mprintf +# define fprintf curl_mfprintf +# define msnprintf curl_msnprintf +# define vprintf curl_mvprintf +# define vfprintf curl_mvfprintf +# define mvsnprintf curl_mvsnprintf +# define aprintf curl_maprintf +# define vaprintf curl_mvaprintf +#endif /* HEADER_CURL_PRINTF_H */ diff --git a/Utilities/cmcurl/lib/curl_range.c b/Utilities/cmcurl/lib/curl_range.c new file mode 100644 index 0000000..d499953 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_range.c @@ -0,0 +1,96 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include <curl/curl.h> +#include "curl_range.h" +#include "sendf.h" +#include "strtoofft.h" + +/* Only include this function if one or more of FTP, FILE are enabled. */ +#if !defined(CURL_DISABLE_FTP) || !defined(CURL_DISABLE_FILE) + + /* + Check if this is a range download, and if so, set the internal variables + properly. + */ +CURLcode Curl_range(struct Curl_easy *data) +{ + curl_off_t from, to; + char *ptr; + char *ptr2; + + if(data->state.use_range && data->state.range) { + CURLofft from_t; + CURLofft to_t; + from_t = curlx_strtoofft(data->state.range, &ptr, 10, &from); + if(from_t == CURL_OFFT_FLOW) + return CURLE_RANGE_ERROR; + while(*ptr && (ISBLANK(*ptr) || (*ptr == '-'))) + ptr++; + to_t = curlx_strtoofft(ptr, &ptr2, 10, &to); + if(to_t == CURL_OFFT_FLOW) + return CURLE_RANGE_ERROR; + if((to_t == CURL_OFFT_INVAL) && !from_t) { + /* X - */ + data->state.resume_from = from; + DEBUGF(infof(data, "RANGE %" CURL_FORMAT_CURL_OFF_T " to end of file", + from)); + } + else if((from_t == CURL_OFFT_INVAL) && !to_t) { + /* -Y */ + data->req.maxdownload = to; + data->state.resume_from = -to; + DEBUGF(infof(data, "RANGE the last %" CURL_FORMAT_CURL_OFF_T " bytes", + to)); + } + else { + /* X-Y */ + curl_off_t totalsize; + + /* Ensure the range is sensible - to should follow from. */ + if(from > to) + return CURLE_RANGE_ERROR; + + totalsize = to - from; + if(totalsize == CURL_OFF_T_MAX) + return CURLE_RANGE_ERROR; + + data->req.maxdownload = totalsize + 1; /* include last byte */ + data->state.resume_from = from; + DEBUGF(infof(data, "RANGE from %" CURL_FORMAT_CURL_OFF_T + " getting %" CURL_FORMAT_CURL_OFF_T " bytes", + from, data->req.maxdownload)); + } + DEBUGF(infof(data, "range-download from %" CURL_FORMAT_CURL_OFF_T + " to %" CURL_FORMAT_CURL_OFF_T ", totally %" + CURL_FORMAT_CURL_OFF_T " bytes", + from, to, data->req.maxdownload)); + } + else + data->req.maxdownload = -1; + return CURLE_OK; +} + +#endif diff --git a/Utilities/cmcurl/lib/curl_range.h b/Utilities/cmcurl/lib/curl_range.h new file mode 100644 index 0000000..77679e2 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_range.h @@ -0,0 +1,31 @@ +#ifndef HEADER_CURL_RANGE_H +#define HEADER_CURL_RANGE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "urldata.h" + +CURLcode Curl_range(struct Curl_easy *data); +#endif /* HEADER_CURL_RANGE_H */ diff --git a/Utilities/cmcurl/lib/curl_rtmp.c b/Utilities/cmcurl/lib/curl_rtmp.c new file mode 100644 index 0000000..f7cf54e --- /dev/null +++ b/Utilities/cmcurl/lib/curl_rtmp.c @@ -0,0 +1,338 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Howard Chu, <hyc@highlandsun.com> + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_LIBRTMP + +#include "curl_rtmp.h" +#include "urldata.h" +#include "nonblock.h" /* for curlx_nonblock */ +#include "progress.h" /* for Curl_pgrsSetUploadSize */ +#include "transfer.h" +#include "warnless.h" +#include <curl/curl.h> +#include <librtmp/rtmp.h> +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +#if defined(_WIN32) && !defined(USE_LWIPSOCK) +#define setsockopt(a,b,c,d,e) (setsockopt)(a,b,c,(const char *)d,(int)e) +#define SET_RCVTIMEO(tv,s) int tv = s*1000 +#elif defined(LWIP_SO_SNDRCVTIMEO_NONSTANDARD) +#define SET_RCVTIMEO(tv,s) int tv = s*1000 +#else +#define SET_RCVTIMEO(tv,s) struct timeval tv = {s,0} +#endif + +#define DEF_BUFTIME (2*60*60*1000) /* 2 hours */ + +static CURLcode rtmp_setup_connection(struct Curl_easy *data, + struct connectdata *conn); +static CURLcode rtmp_do(struct Curl_easy *data, bool *done); +static CURLcode rtmp_done(struct Curl_easy *data, CURLcode, bool premature); +static CURLcode rtmp_connect(struct Curl_easy *data, bool *done); +static CURLcode rtmp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead); + +static Curl_recv rtmp_recv; +static Curl_send rtmp_send; + +/* + * RTMP protocol handler.h, based on https://rtmpdump.mplayerhq.hu + */ + +const struct Curl_handler Curl_handler_rtmp = { + "RTMP", /* scheme */ + rtmp_setup_connection, /* setup_connection */ + rtmp_do, /* do_it */ + rtmp_done, /* done */ + ZERO_NULL, /* do_more */ + rtmp_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + rtmp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_RTMP, /* defport */ + CURLPROTO_RTMP, /* protocol */ + CURLPROTO_RTMP, /* family */ + PROTOPT_NONE /* flags */ +}; + +const struct Curl_handler Curl_handler_rtmpt = { + "RTMPT", /* scheme */ + rtmp_setup_connection, /* setup_connection */ + rtmp_do, /* do_it */ + rtmp_done, /* done */ + ZERO_NULL, /* do_more */ + rtmp_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + rtmp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_RTMPT, /* defport */ + CURLPROTO_RTMPT, /* protocol */ + CURLPROTO_RTMPT, /* family */ + PROTOPT_NONE /* flags */ +}; + +const struct Curl_handler Curl_handler_rtmpe = { + "RTMPE", /* scheme */ + rtmp_setup_connection, /* setup_connection */ + rtmp_do, /* do_it */ + rtmp_done, /* done */ + ZERO_NULL, /* do_more */ + rtmp_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + rtmp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_RTMP, /* defport */ + CURLPROTO_RTMPE, /* protocol */ + CURLPROTO_RTMPE, /* family */ + PROTOPT_NONE /* flags */ +}; + +const struct Curl_handler Curl_handler_rtmpte = { + "RTMPTE", /* scheme */ + rtmp_setup_connection, /* setup_connection */ + rtmp_do, /* do_it */ + rtmp_done, /* done */ + ZERO_NULL, /* do_more */ + rtmp_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + rtmp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_RTMPT, /* defport */ + CURLPROTO_RTMPTE, /* protocol */ + CURLPROTO_RTMPTE, /* family */ + PROTOPT_NONE /* flags */ +}; + +const struct Curl_handler Curl_handler_rtmps = { + "RTMPS", /* scheme */ + rtmp_setup_connection, /* setup_connection */ + rtmp_do, /* do_it */ + rtmp_done, /* done */ + ZERO_NULL, /* do_more */ + rtmp_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + rtmp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_RTMPS, /* defport */ + CURLPROTO_RTMPS, /* protocol */ + CURLPROTO_RTMP, /* family */ + PROTOPT_NONE /* flags */ +}; + +const struct Curl_handler Curl_handler_rtmpts = { + "RTMPTS", /* scheme */ + rtmp_setup_connection, /* setup_connection */ + rtmp_do, /* do_it */ + rtmp_done, /* done */ + ZERO_NULL, /* do_more */ + rtmp_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + rtmp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_RTMPS, /* defport */ + CURLPROTO_RTMPTS, /* protocol */ + CURLPROTO_RTMPT, /* family */ + PROTOPT_NONE /* flags */ +}; + +static CURLcode rtmp_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + RTMP *r = RTMP_Alloc(); + if(!r) + return CURLE_OUT_OF_MEMORY; + + RTMP_Init(r); + RTMP_SetBufferMS(r, DEF_BUFTIME); + if(!RTMP_SetupURL(r, data->state.url)) { + RTMP_Free(r); + return CURLE_URL_MALFORMAT; + } + conn->proto.rtmp = r; + return CURLE_OK; +} + +static CURLcode rtmp_connect(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + RTMP *r = conn->proto.rtmp; + SET_RCVTIMEO(tv, 10); + + r->m_sb.sb_socket = (int)conn->sock[FIRSTSOCKET]; + + /* We have to know if it's a write before we send the + * connect request packet + */ + if(data->state.upload) + r->Link.protocol |= RTMP_FEATURE_WRITE; + + /* For plain streams, use the buffer toggle trick to keep data flowing */ + if(!(r->Link.lFlags & RTMP_LF_LIVE) && + !(r->Link.protocol & RTMP_FEATURE_HTTP)) + r->Link.lFlags |= RTMP_LF_BUFX; + + (void)curlx_nonblock(r->m_sb.sb_socket, FALSE); + setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, + (char *)&tv, sizeof(tv)); + + if(!RTMP_Connect1(r, NULL)) + return CURLE_FAILED_INIT; + + /* Clients must send a periodic BytesReceived report to the server */ + r->m_bSendCounter = true; + + *done = TRUE; + conn->recv[FIRSTSOCKET] = rtmp_recv; + conn->send[FIRSTSOCKET] = rtmp_send; + return CURLE_OK; +} + +static CURLcode rtmp_do(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + RTMP *r = conn->proto.rtmp; + + if(!RTMP_ConnectStream(r, 0)) + return CURLE_FAILED_INIT; + + if(data->state.upload) { + Curl_pgrsSetUploadSize(data, data->state.infilesize); + Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + } + else + Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + *done = TRUE; + return CURLE_OK; +} + +static CURLcode rtmp_done(struct Curl_easy *data, CURLcode status, + bool premature) +{ + (void)data; /* unused */ + (void)status; /* unused */ + (void)premature; /* unused */ + + return CURLE_OK; +} + +static CURLcode rtmp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + RTMP *r = conn->proto.rtmp; + (void)data; + (void)dead_connection; + if(r) { + conn->proto.rtmp = NULL; + RTMP_Close(r); + RTMP_Free(r); + } + return CURLE_OK; +} + +static ssize_t rtmp_recv(struct Curl_easy *data, int sockindex, char *buf, + size_t len, CURLcode *err) +{ + struct connectdata *conn = data->conn; + RTMP *r = conn->proto.rtmp; + ssize_t nread; + + (void)sockindex; /* unused */ + + nread = RTMP_Read(r, buf, curlx_uztosi(len)); + if(nread < 0) { + if(r->m_read.status == RTMP_READ_COMPLETE || + r->m_read.status == RTMP_READ_EOF) { + data->req.size = data->req.bytecount; + nread = 0; + } + else + *err = CURLE_RECV_ERROR; + } + return nread; +} + +static ssize_t rtmp_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t len, CURLcode *err) +{ + struct connectdata *conn = data->conn; + RTMP *r = conn->proto.rtmp; + ssize_t num; + + (void)sockindex; /* unused */ + + num = RTMP_Write(r, (char *)buf, curlx_uztosi(len)); + if(num < 0) + *err = CURLE_SEND_ERROR; + + return num; +} +#endif /* USE_LIBRTMP */ diff --git a/Utilities/cmcurl/lib/curl_rtmp.h b/Utilities/cmcurl/lib/curl_rtmp.h new file mode 100644 index 0000000..9b93ee0 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_rtmp.h @@ -0,0 +1,35 @@ +#ifndef HEADER_CURL_RTMP_H +#define HEADER_CURL_RTMP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Howard Chu, <hyc@highlandsun.com> + * + * 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 + * + ***************************************************************************/ +#ifdef USE_LIBRTMP +extern const struct Curl_handler Curl_handler_rtmp; +extern const struct Curl_handler Curl_handler_rtmpt; +extern const struct Curl_handler Curl_handler_rtmpe; +extern const struct Curl_handler Curl_handler_rtmpte; +extern const struct Curl_handler Curl_handler_rtmps; +extern const struct Curl_handler Curl_handler_rtmpts; +#endif + +#endif /* HEADER_CURL_RTMP_H */ diff --git a/Utilities/cmcurl/lib/curl_sasl.c b/Utilities/cmcurl/lib/curl_sasl.c new file mode 100644 index 0000000..78ad298 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_sasl.c @@ -0,0 +1,755 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC2195 CRAM-MD5 authentication + * RFC2617 Basic and Digest Access Authentication + * RFC2831 DIGEST-MD5 authentication + * RFC4422 Simple Authentication and Security Layer (SASL) + * RFC4616 PLAIN authentication + * RFC5802 SCRAM-SHA-1 authentication + * RFC7677 SCRAM-SHA-256 authentication + * RFC6749 OAuth 2.0 Authorization Framework + * RFC7628 A Set of SASL Mechanisms for OAuth + * Draft LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt> + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_POP3) || \ + (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)) + +#include <curl/curl.h> +#include "urldata.h" + +#include "curl_base64.h" +#include "curl_md5.h" +#include "vauth/vauth.h" +#include "cfilters.h" +#include "vtls/vtls.h" +#include "curl_hmac.h" +#include "curl_sasl.h" +#include "warnless.h" +#include "strtok.h" +#include "sendf.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* Supported mechanisms */ +static const struct { + const char *name; /* Name */ + size_t len; /* Name length */ + unsigned short bit; /* Flag bit */ +} mechtable[] = { + { "LOGIN", 5, SASL_MECH_LOGIN }, + { "PLAIN", 5, SASL_MECH_PLAIN }, + { "CRAM-MD5", 8, SASL_MECH_CRAM_MD5 }, + { "DIGEST-MD5", 10, SASL_MECH_DIGEST_MD5 }, + { "GSSAPI", 6, SASL_MECH_GSSAPI }, + { "EXTERNAL", 8, SASL_MECH_EXTERNAL }, + { "NTLM", 4, SASL_MECH_NTLM }, + { "XOAUTH2", 7, SASL_MECH_XOAUTH2 }, + { "OAUTHBEARER", 11, SASL_MECH_OAUTHBEARER }, + { "SCRAM-SHA-1", 11, SASL_MECH_SCRAM_SHA_1 }, + { "SCRAM-SHA-256",13, SASL_MECH_SCRAM_SHA_256 }, + { ZERO_NULL, 0, 0 } +}; + +/* + * Curl_sasl_cleanup() + * + * This is used to cleanup any libraries or curl modules used by the sasl + * functions. + * + * Parameters: + * + * conn [in] - The connection data. + * authused [in] - The authentication mechanism used. + */ +void Curl_sasl_cleanup(struct connectdata *conn, unsigned short authused) +{ + (void)conn; + (void)authused; + +#if defined(USE_KERBEROS5) + /* Cleanup the gssapi structure */ + if(authused == SASL_MECH_GSSAPI) { + Curl_auth_cleanup_gssapi(&conn->krb5); + } +#endif + +#if defined(USE_GSASL) + /* Cleanup the GSASL structure */ + if(authused & (SASL_MECH_SCRAM_SHA_1 | SASL_MECH_SCRAM_SHA_256)) { + Curl_auth_gsasl_cleanup(&conn->gsasl); + } +#endif + +#if defined(USE_NTLM) + /* Cleanup the NTLM structure */ + if(authused == SASL_MECH_NTLM) { + Curl_auth_cleanup_ntlm(&conn->ntlm); + } +#endif +} + +/* + * Curl_sasl_decode_mech() + * + * Convert a SASL mechanism name into a token. + * + * Parameters: + * + * ptr [in] - The mechanism string. + * maxlen [in] - Maximum mechanism string length. + * len [out] - If not NULL, effective name length. + * + * Returns the SASL mechanism token or 0 if no match. + */ +unsigned short Curl_sasl_decode_mech(const char *ptr, size_t maxlen, + size_t *len) +{ + unsigned int i; + char c; + + for(i = 0; mechtable[i].name; i++) { + if(maxlen >= mechtable[i].len && + !memcmp(ptr, mechtable[i].name, mechtable[i].len)) { + if(len) + *len = mechtable[i].len; + + if(maxlen == mechtable[i].len) + return mechtable[i].bit; + + c = ptr[mechtable[i].len]; + if(!ISUPPER(c) && !ISDIGIT(c) && c != '-' && c != '_') + return mechtable[i].bit; + } + } + + return 0; +} + +/* + * Curl_sasl_parse_url_auth_option() + * + * Parse the URL login options. + */ +CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl, + const char *value, size_t len) +{ + CURLcode result = CURLE_OK; + size_t mechlen; + + if(!len) + return CURLE_URL_MALFORMAT; + + if(sasl->resetprefs) { + sasl->resetprefs = FALSE; + sasl->prefmech = SASL_AUTH_NONE; + } + + if(!strncmp(value, "*", len)) + sasl->prefmech = SASL_AUTH_DEFAULT; + else { + unsigned short mechbit = Curl_sasl_decode_mech(value, len, &mechlen); + if(mechbit && mechlen == len) + sasl->prefmech |= mechbit; + else + result = CURLE_URL_MALFORMAT; + } + + return result; +} + +/* + * Curl_sasl_init() + * + * Initializes the SASL structure. + */ +void Curl_sasl_init(struct SASL *sasl, struct Curl_easy *data, + const struct SASLproto *params) +{ + unsigned long auth = data->set.httpauth; + + sasl->params = params; /* Set protocol dependent parameters */ + sasl->state = SASL_STOP; /* Not yet running */ + sasl->curmech = NULL; /* No mechanism yet. */ + sasl->authmechs = SASL_AUTH_NONE; /* No known authentication mechanism yet */ + sasl->prefmech = params->defmechs; /* Default preferred mechanisms */ + sasl->authused = SASL_AUTH_NONE; /* The authentication mechanism used */ + sasl->resetprefs = TRUE; /* Reset prefmech upon AUTH parsing. */ + sasl->mutual_auth = FALSE; /* No mutual authentication (GSSAPI only) */ + sasl->force_ir = FALSE; /* Respect external option */ + + if(auth != CURLAUTH_BASIC) { + sasl->resetprefs = FALSE; + sasl->prefmech = SASL_AUTH_NONE; + if(auth & CURLAUTH_BASIC) + sasl->prefmech |= SASL_MECH_PLAIN | SASL_MECH_LOGIN; + if(auth & CURLAUTH_DIGEST) + sasl->prefmech |= SASL_MECH_DIGEST_MD5; + if(auth & CURLAUTH_NTLM) + sasl->prefmech |= SASL_MECH_NTLM; + if(auth & CURLAUTH_BEARER) + sasl->prefmech |= SASL_MECH_OAUTHBEARER | SASL_MECH_XOAUTH2; + if(auth & CURLAUTH_GSSAPI) + sasl->prefmech |= SASL_MECH_GSSAPI; + } +} + +/* + * sasl_state() + * + * This is the ONLY way to change SASL state! + */ +static void sasl_state(struct SASL *sasl, struct Curl_easy *data, + saslstate newstate) +{ +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[]={ + "STOP", + "PLAIN", + "LOGIN", + "LOGIN_PASSWD", + "EXTERNAL", + "CRAMMD5", + "DIGESTMD5", + "DIGESTMD5_RESP", + "NTLM", + "NTLM_TYPE2MSG", + "GSSAPI", + "GSSAPI_TOKEN", + "GSSAPI_NO_DATA", + "OAUTH2", + "OAUTH2_RESP", + "GSASL", + "CANCEL", + "FINAL", + /* LAST */ + }; + + if(sasl->state != newstate) + infof(data, "SASL %p state change from %s to %s", + (void *)sasl, names[sasl->state], names[newstate]); +#else + (void) data; +#endif + + sasl->state = newstate; +} + +#if defined(USE_NTLM) || defined(USE_GSASL) || defined(USE_KERBEROS5) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) +/* Get the SASL server message and convert it to binary. */ +static CURLcode get_server_message(struct SASL *sasl, struct Curl_easy *data, + struct bufref *out) +{ + CURLcode result = CURLE_OK; + + result = sasl->params->getmessage(data, out); + if(!result && (sasl->params->flags & SASL_FLAG_BASE64)) { + unsigned char *msg; + size_t msglen; + const char *serverdata = (const char *) Curl_bufref_ptr(out); + + if(!*serverdata || *serverdata == '=') + Curl_bufref_set(out, NULL, 0, NULL); + else { + result = Curl_base64_decode(serverdata, &msg, &msglen); + if(!result) + Curl_bufref_set(out, msg, msglen, curl_free); + } + } + return result; +} +#endif + +/* Encode the outgoing SASL message. */ +static CURLcode build_message(struct SASL *sasl, struct bufref *msg) +{ + CURLcode result = CURLE_OK; + + 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 { + char *base64; + size_t base64len; + + result = Curl_base64_encode((const char *) Curl_bufref_ptr(msg), + Curl_bufref_len(msg), &base64, &base64len); + if(!result) + Curl_bufref_set(msg, base64, base64len, curl_free); + } + } + + return result; +} + +/* + * Curl_sasl_can_authenticate() + * + * Check if we have enough auth data and capabilities to authenticate. + */ +bool Curl_sasl_can_authenticate(struct SASL *sasl, struct Curl_easy *data) +{ + /* Have credentials been provided? */ + if(data->state.aptr.user) + return TRUE; + + /* EXTERNAL can authenticate without a user name and/or password */ + if(sasl->authmechs & sasl->prefmech & SASL_MECH_EXTERNAL) + return TRUE; + + return FALSE; +} + +/* + * Curl_sasl_start() + * + * Calculate the required login details for SASL authentication. + */ +CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, + bool force_ir, saslprogress *progress) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + unsigned short enabledmechs; + const char *mech = NULL; + struct bufref resp; + saslstate state1 = SASL_STOP; + saslstate state2 = SASL_FINAL; + const char *hostname, *disp_hostname; + int port; +#if defined(USE_KERBEROS5) || defined(USE_NTLM) + const char *service = data->set.str[STRING_SERVICE_NAME] ? + data->set.str[STRING_SERVICE_NAME] : + sasl->params->service; +#endif + const char *oauth_bearer = data->set.str[STRING_BEARER]; + struct bufref nullmsg; + + Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &disp_hostname, &port); + Curl_bufref_init(&nullmsg); + Curl_bufref_init(&resp); + sasl->force_ir = force_ir; /* Latch for future use */ + sasl->authused = 0; /* No mechanism used yet */ + enabledmechs = sasl->authmechs & sasl->prefmech; + *progress = SASL_IDLE; + + /* Calculate the supported authentication mechanism, by decreasing order of + security, as well as the initial response where appropriate */ + if((enabledmechs & SASL_MECH_EXTERNAL) && !conn->passwd[0]) { + mech = SASL_MECH_STRING_EXTERNAL; + state1 = SASL_EXTERNAL; + sasl->authused = SASL_MECH_EXTERNAL; + + if(force_ir || data->set.sasl_ir) + result = Curl_auth_create_external_message(conn->user, &resp); + } + else if(data->state.aptr.user) { +#if defined(USE_KERBEROS5) + if((enabledmechs & SASL_MECH_GSSAPI) && Curl_auth_is_gssapi_supported() && + Curl_auth_user_contains_domain(conn->user)) { + sasl->mutual_auth = FALSE; + mech = SASL_MECH_STRING_GSSAPI; + state1 = SASL_GSSAPI; + state2 = SASL_GSSAPI_TOKEN; + sasl->authused = SASL_MECH_GSSAPI; + + if(force_ir || data->set.sasl_ir) + result = Curl_auth_create_gssapi_user_message(data, conn->user, + conn->passwd, + service, + conn->host.name, + sasl->mutual_auth, + NULL, &conn->krb5, + &resp); + } + else +#endif +#ifdef USE_GSASL + if((enabledmechs & SASL_MECH_SCRAM_SHA_256) && + Curl_auth_gsasl_is_supported(data, SASL_MECH_STRING_SCRAM_SHA_256, + &conn->gsasl)) { + mech = SASL_MECH_STRING_SCRAM_SHA_256; + sasl->authused = SASL_MECH_SCRAM_SHA_256; + state1 = SASL_GSASL; + state2 = SASL_GSASL; + + result = Curl_auth_gsasl_start(data, conn->user, + conn->passwd, &conn->gsasl); + if(result == CURLE_OK && (force_ir || data->set.sasl_ir)) + result = Curl_auth_gsasl_token(data, &nullmsg, &conn->gsasl, &resp); + } + else if((enabledmechs & SASL_MECH_SCRAM_SHA_1) && + Curl_auth_gsasl_is_supported(data, SASL_MECH_STRING_SCRAM_SHA_1, + &conn->gsasl)) { + mech = SASL_MECH_STRING_SCRAM_SHA_1; + sasl->authused = SASL_MECH_SCRAM_SHA_1; + state1 = SASL_GSASL; + state2 = SASL_GSASL; + + result = Curl_auth_gsasl_start(data, conn->user, + conn->passwd, &conn->gsasl); + if(result == CURLE_OK && (force_ir || data->set.sasl_ir)) + result = Curl_auth_gsasl_token(data, &nullmsg, &conn->gsasl, &resp); + } + else +#endif +#ifndef CURL_DISABLE_DIGEST_AUTH + if((enabledmechs & SASL_MECH_DIGEST_MD5) && + Curl_auth_is_digest_supported()) { + mech = SASL_MECH_STRING_DIGEST_MD5; + state1 = SASL_DIGESTMD5; + sasl->authused = SASL_MECH_DIGEST_MD5; + } + else if(enabledmechs & SASL_MECH_CRAM_MD5) { + mech = SASL_MECH_STRING_CRAM_MD5; + state1 = SASL_CRAMMD5; + sasl->authused = SASL_MECH_CRAM_MD5; + } + else +#endif +#ifdef USE_NTLM + if((enabledmechs & SASL_MECH_NTLM) && Curl_auth_is_ntlm_supported()) { + mech = SASL_MECH_STRING_NTLM; + state1 = SASL_NTLM; + state2 = SASL_NTLM_TYPE2MSG; + sasl->authused = SASL_MECH_NTLM; + + if(force_ir || data->set.sasl_ir) + result = Curl_auth_create_ntlm_type1_message(data, + conn->user, conn->passwd, + service, + hostname, + &conn->ntlm, &resp); + } + else +#endif + if((enabledmechs & SASL_MECH_OAUTHBEARER) && oauth_bearer) { + mech = SASL_MECH_STRING_OAUTHBEARER; + state1 = SASL_OAUTH2; + state2 = SASL_OAUTH2_RESP; + sasl->authused = SASL_MECH_OAUTHBEARER; + + if(force_ir || data->set.sasl_ir) + result = Curl_auth_create_oauth_bearer_message(conn->user, + hostname, + port, + oauth_bearer, + &resp); + } + else if((enabledmechs & SASL_MECH_XOAUTH2) && oauth_bearer) { + mech = SASL_MECH_STRING_XOAUTH2; + state1 = SASL_OAUTH2; + sasl->authused = SASL_MECH_XOAUTH2; + + if(force_ir || data->set.sasl_ir) + result = Curl_auth_create_xoauth_bearer_message(conn->user, + oauth_bearer, + &resp); + } + else if(enabledmechs & SASL_MECH_PLAIN) { + mech = SASL_MECH_STRING_PLAIN; + state1 = SASL_PLAIN; + sasl->authused = SASL_MECH_PLAIN; + + if(force_ir || data->set.sasl_ir) + result = Curl_auth_create_plain_message(conn->sasl_authzid, + conn->user, conn->passwd, + &resp); + } + else if(enabledmechs & SASL_MECH_LOGIN) { + mech = SASL_MECH_STRING_LOGIN; + state1 = SASL_LOGIN; + state2 = SASL_LOGIN_PASSWD; + sasl->authused = SASL_MECH_LOGIN; + + if(force_ir || data->set.sasl_ir) + result = Curl_auth_create_login_message(conn->user, &resp); + } + } + + if(!result && mech) { + sasl->curmech = mech; + if(Curl_bufref_ptr(&resp)) + result = build_message(sasl, &resp); + + if(sasl->params->maxirlen && + strlen(mech) + Curl_bufref_len(&resp) > sasl->params->maxirlen) + Curl_bufref_free(&resp); + + if(!result) + result = sasl->params->sendauth(data, mech, &resp); + + if(!result) { + *progress = SASL_INPROGRESS; + sasl_state(sasl, data, Curl_bufref_ptr(&resp) ? state2 : state1); + } + } + + Curl_bufref_free(&resp); + return result; +} + +/* + * Curl_sasl_continue() + * + * Continue the authentication. + */ +CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, + int code, saslprogress *progress) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + saslstate newstate = SASL_FINAL; + struct bufref resp; + const char *hostname, *disp_hostname; + int port; +#if defined(USE_KERBEROS5) || defined(USE_NTLM) \ + || !defined(CURL_DISABLE_DIGEST_AUTH) + const char *service = data->set.str[STRING_SERVICE_NAME] ? + data->set.str[STRING_SERVICE_NAME] : + sasl->params->service; +#endif + const char *oauth_bearer = data->set.str[STRING_BEARER]; + struct bufref serverdata; + + Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &disp_hostname, &port); + Curl_bufref_init(&serverdata); + Curl_bufref_init(&resp); + *progress = SASL_INPROGRESS; + + if(sasl->state == SASL_FINAL) { + if(code != sasl->params->finalcode) + result = CURLE_LOGIN_DENIED; + *progress = SASL_DONE; + sasl_state(sasl, data, SASL_STOP); + return result; + } + + if(sasl->state != SASL_CANCEL && sasl->state != SASL_OAUTH2_RESP && + code != sasl->params->contcode) { + *progress = SASL_DONE; + sasl_state(sasl, data, SASL_STOP); + return CURLE_LOGIN_DENIED; + } + + switch(sasl->state) { + case SASL_STOP: + *progress = SASL_DONE; + return result; + case SASL_PLAIN: + result = Curl_auth_create_plain_message(conn->sasl_authzid, + conn->user, conn->passwd, &resp); + break; + case SASL_LOGIN: + result = Curl_auth_create_login_message(conn->user, &resp); + newstate = SASL_LOGIN_PASSWD; + break; + case SASL_LOGIN_PASSWD: + result = Curl_auth_create_login_message(conn->passwd, &resp); + break; + case SASL_EXTERNAL: + result = Curl_auth_create_external_message(conn->user, &resp); + break; +#ifdef USE_GSASL + case SASL_GSASL: + result = get_server_message(sasl, data, &serverdata); + if(!result) + result = Curl_auth_gsasl_token(data, &serverdata, &conn->gsasl, &resp); + if(!result && Curl_bufref_len(&resp) > 0) + newstate = SASL_GSASL; + break; +#endif +#ifndef CURL_DISABLE_DIGEST_AUTH + case SASL_CRAMMD5: + result = get_server_message(sasl, data, &serverdata); + if(!result) + result = Curl_auth_create_cram_md5_message(&serverdata, conn->user, + conn->passwd, &resp); + break; + case SASL_DIGESTMD5: + result = get_server_message(sasl, data, &serverdata); + if(!result) + result = Curl_auth_create_digest_md5_message(data, &serverdata, + conn->user, conn->passwd, + service, &resp); + if(!result && (sasl->params->flags & SASL_FLAG_BASE64)) + newstate = SASL_DIGESTMD5_RESP; + break; + case SASL_DIGESTMD5_RESP: + /* Keep response NULL to output an empty line. */ + break; +#endif + +#ifdef USE_NTLM + case SASL_NTLM: + /* Create the type-1 message */ + result = Curl_auth_create_ntlm_type1_message(data, + conn->user, conn->passwd, + service, hostname, + &conn->ntlm, &resp); + newstate = SASL_NTLM_TYPE2MSG; + break; + case SASL_NTLM_TYPE2MSG: + /* Decode the type-2 message */ + result = get_server_message(sasl, data, &serverdata); + if(!result) + result = Curl_auth_decode_ntlm_type2_message(data, &serverdata, + &conn->ntlm); + if(!result) + result = Curl_auth_create_ntlm_type3_message(data, conn->user, + conn->passwd, &conn->ntlm, + &resp); + break; +#endif + +#if defined(USE_KERBEROS5) + case SASL_GSSAPI: + result = Curl_auth_create_gssapi_user_message(data, conn->user, + conn->passwd, + service, + conn->host.name, + sasl->mutual_auth, NULL, + &conn->krb5, + &resp); + newstate = SASL_GSSAPI_TOKEN; + break; + case SASL_GSSAPI_TOKEN: + result = get_server_message(sasl, data, &serverdata); + if(!result) { + if(sasl->mutual_auth) { + /* Decode the user token challenge and create the optional response + message */ + result = Curl_auth_create_gssapi_user_message(data, NULL, NULL, + NULL, NULL, + sasl->mutual_auth, + &serverdata, + &conn->krb5, + &resp); + newstate = SASL_GSSAPI_NO_DATA; + } + else + /* Decode the security challenge and create the response message */ + result = Curl_auth_create_gssapi_security_message(data, + conn->sasl_authzid, + &serverdata, + &conn->krb5, + &resp); + } + break; + case SASL_GSSAPI_NO_DATA: + /* Decode the security challenge and create the response message */ + result = get_server_message(sasl, data, &serverdata); + if(!result) + result = Curl_auth_create_gssapi_security_message(data, + conn->sasl_authzid, + &serverdata, + &conn->krb5, + &resp); + break; +#endif + + case SASL_OAUTH2: + /* Create the authorization message */ + if(sasl->authused == SASL_MECH_OAUTHBEARER) { + result = Curl_auth_create_oauth_bearer_message(conn->user, + hostname, + port, + oauth_bearer, + &resp); + + /* Failures maybe sent by the server as continuations for OAUTHBEARER */ + newstate = SASL_OAUTH2_RESP; + } + else + result = Curl_auth_create_xoauth_bearer_message(conn->user, + oauth_bearer, + &resp); + break; + + case SASL_OAUTH2_RESP: + /* The continuation is optional so check the response code */ + if(code == sasl->params->finalcode) { + /* Final response was received so we are done */ + *progress = SASL_DONE; + sasl_state(sasl, data, SASL_STOP); + return result; + } + else if(code == sasl->params->contcode) { + /* Acknowledge the continuation by sending a 0x01 response. */ + Curl_bufref_set(&resp, "\x01", 1, NULL); + break; + } + else { + *progress = SASL_DONE; + sasl_state(sasl, data, SASL_STOP); + return CURLE_LOGIN_DENIED; + } + + case SASL_CANCEL: + /* Remove the offending mechanism from the supported list */ + sasl->authmechs ^= sasl->authused; + + /* Start an alternative SASL authentication */ + return Curl_sasl_start(sasl, data, sasl->force_ir, progress); + default: + failf(data, "Unsupported SASL authentication mechanism"); + result = CURLE_UNSUPPORTED_PROTOCOL; /* Should not happen */ + break; + } + + Curl_bufref_free(&serverdata); + + switch(result) { + case CURLE_BAD_CONTENT_ENCODING: + /* Cancel dialog */ + result = sasl->params->cancelauth(data, sasl->curmech); + newstate = SASL_CANCEL; + break; + case CURLE_OK: + result = build_message(sasl, &resp); + if(!result) + result = sasl->params->contauth(data, sasl->curmech, &resp); + break; + default: + newstate = SASL_STOP; /* Stop on error */ + *progress = SASL_DONE; + break; + } + + Curl_bufref_free(&resp); + + sasl_state(sasl, data, newstate); + + return result; +} +#endif /* protocols are enabled that use SASL */ diff --git a/Utilities/cmcurl/lib/curl_sasl.h b/Utilities/cmcurl/lib/curl_sasl.h new file mode 100644 index 0000000..e94e643 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_sasl.h @@ -0,0 +1,165 @@ +#ifndef HEADER_CURL_SASL_H +#define HEADER_CURL_SASL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include <curl/curl.h> + +#include "bufref.h" + +struct Curl_easy; +struct connectdata; + +/* Authentication mechanism flags */ +#define SASL_MECH_LOGIN (1 << 0) +#define SASL_MECH_PLAIN (1 << 1) +#define SASL_MECH_CRAM_MD5 (1 << 2) +#define SASL_MECH_DIGEST_MD5 (1 << 3) +#define SASL_MECH_GSSAPI (1 << 4) +#define SASL_MECH_EXTERNAL (1 << 5) +#define SASL_MECH_NTLM (1 << 6) +#define SASL_MECH_XOAUTH2 (1 << 7) +#define SASL_MECH_OAUTHBEARER (1 << 8) +#define SASL_MECH_SCRAM_SHA_1 (1 << 9) +#define SASL_MECH_SCRAM_SHA_256 (1 << 10) + +/* Authentication mechanism values */ +#define SASL_AUTH_NONE 0 +#define SASL_AUTH_ANY 0xffff +#define SASL_AUTH_DEFAULT (SASL_AUTH_ANY & ~SASL_MECH_EXTERNAL) + +/* Authentication mechanism strings */ +#define SASL_MECH_STRING_LOGIN "LOGIN" +#define SASL_MECH_STRING_PLAIN "PLAIN" +#define SASL_MECH_STRING_CRAM_MD5 "CRAM-MD5" +#define SASL_MECH_STRING_DIGEST_MD5 "DIGEST-MD5" +#define SASL_MECH_STRING_GSSAPI "GSSAPI" +#define SASL_MECH_STRING_EXTERNAL "EXTERNAL" +#define SASL_MECH_STRING_NTLM "NTLM" +#define SASL_MECH_STRING_XOAUTH2 "XOAUTH2" +#define SASL_MECH_STRING_OAUTHBEARER "OAUTHBEARER" +#define SASL_MECH_STRING_SCRAM_SHA_1 "SCRAM-SHA-1" +#define SASL_MECH_STRING_SCRAM_SHA_256 "SCRAM-SHA-256" + +/* SASL flags */ +#define SASL_FLAG_BASE64 0x0001 /* Messages are base64-encoded */ + +/* SASL machine states */ +typedef enum { + SASL_STOP, + SASL_PLAIN, + SASL_LOGIN, + SASL_LOGIN_PASSWD, + SASL_EXTERNAL, + SASL_CRAMMD5, + SASL_DIGESTMD5, + SASL_DIGESTMD5_RESP, + SASL_NTLM, + SASL_NTLM_TYPE2MSG, + SASL_GSSAPI, + SASL_GSSAPI_TOKEN, + SASL_GSSAPI_NO_DATA, + SASL_OAUTH2, + SASL_OAUTH2_RESP, + SASL_GSASL, + SASL_CANCEL, + SASL_FINAL +} saslstate; + +/* Progress indicator */ +typedef enum { + SASL_IDLE, + SASL_INPROGRESS, + SASL_DONE +} saslprogress; + +/* Protocol dependent SASL parameters */ +struct SASLproto { + const char *service; /* The service name */ + CURLcode (*sendauth)(struct Curl_easy *data, const char *mech, + const struct bufref *ir); + /* Send authentication command */ + CURLcode (*contauth)(struct Curl_easy *data, const char *mech, + const struct bufref *contauth); + /* Send authentication continuation */ + CURLcode (*cancelauth)(struct Curl_easy *data, const char *mech); + /* Cancel authentication. */ + CURLcode (*getmessage)(struct Curl_easy *data, struct bufref *out); + /* Get SASL response message */ + size_t maxirlen; /* Maximum initial response + mechanism length, + or zero if no max. This is normally the max + command length - other characters count. + This has to be zero for non-base64 protocols. */ + int contcode; /* Code to receive when continuation is expected */ + int finalcode; /* Code to receive upon authentication success */ + unsigned short defmechs; /* Mechanisms enabled by default */ + unsigned short flags; /* Configuration flags. */ +}; + +/* Per-connection parameters */ +struct SASL { + const struct SASLproto *params; /* Protocol dependent parameters */ + saslstate state; /* Current machine state */ + const char *curmech; /* Current mechanism id. */ + unsigned short authmechs; /* Accepted authentication mechanisms */ + unsigned short prefmech; /* Preferred authentication mechanism */ + unsigned short authused; /* Auth mechanism used for the connection */ + BIT(resetprefs); /* For URL auth option parsing. */ + BIT(mutual_auth); /* Mutual authentication enabled (GSSAPI only) */ + BIT(force_ir); /* Protocol always supports initial response */ +}; + +/* This is used to test whether the line starts with the given mechanism */ +#define sasl_mech_equal(line, wordlen, mech) \ + (wordlen == (sizeof(mech) - 1) / sizeof(char) && \ + !memcmp(line, mech, wordlen)) + +/* This is used to cleanup any libraries or curl modules used by the sasl + functions */ +void Curl_sasl_cleanup(struct connectdata *conn, unsigned short authused); + +/* Convert a mechanism name to a token */ +unsigned short Curl_sasl_decode_mech(const char *ptr, + size_t maxlen, size_t *len); + +/* Parse the URL login options */ +CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl, + const char *value, size_t len); + +/* Initializes an SASL structure */ +void Curl_sasl_init(struct SASL *sasl, struct Curl_easy *data, + const struct SASLproto *params); + +/* Check if we have enough auth data and capabilities to authenticate */ +bool Curl_sasl_can_authenticate(struct SASL *sasl, struct Curl_easy *data); + +/* Calculate the required login details for SASL authentication */ +CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, + bool force_ir, saslprogress *progress); + +/* Continue an SASL authentication */ +CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, + int code, saslprogress *progress); + +#endif /* HEADER_CURL_SASL_H */ diff --git a/Utilities/cmcurl/lib/curl_setup.h b/Utilities/cmcurl/lib/curl_setup.h new file mode 100644 index 0000000..7fe6397 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_setup.h @@ -0,0 +1,817 @@ +#ifndef HEADER_CURL_SETUP_H +#define HEADER_CURL_SETUP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#if defined(BUILDING_LIBCURL) && !defined(CURL_NO_OLDIES) +#define CURL_NO_OLDIES +#endif + +/* Set default _WIN32_WINNT */ +#ifdef __MINGW32__ +#include <_mingw.h> +#endif + +/* + * Disable Visual Studio warnings: + * 4127 "conditional expression is constant" + */ +#ifdef _MSC_VER +#pragma warning(disable:4127) +#endif + +#ifdef _WIN32 +/* + * Don't include unneeded stuff in Windows headers to avoid compiler + * warnings and macro clashes. + * Make sure to define this macro before including any Windows headers. + */ +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# ifndef NOGDI +# define NOGDI +# endif +/* Detect Windows App environment which has a restricted access + * to the Win32 APIs. */ +# if (defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0602)) || \ + defined(WINAPI_FAMILY) +# include <winapifamily.h> +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && \ + !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# define CURL_WINDOWS_APP +# endif +# endif +#endif + +/* + * Include configuration script results or hand-crafted + * configuration file for platforms which lack config tool. + */ + +#ifdef HAVE_CONFIG_H + +#include "curl_config.h" + +#else /* HAVE_CONFIG_H */ + +#ifdef _WIN32_WCE +# include "config-win32ce.h" +#else +# ifdef _WIN32 +# include "config-win32.h" +# endif +#endif + +#ifdef macintosh +# include "config-mac.h" +#endif + +#ifdef __riscos__ +# include "config-riscos.h" +#endif + +#ifdef __AMIGA__ +# include "config-amigaos.h" +#endif + +#ifdef __OS400__ +# include "config-os400.h" +#endif + +#ifdef __PLAN9__ +# include "config-plan9.h" +#endif + +#ifdef MSDOS +# include "config-dos.h" +#endif + +#endif /* HAVE_CONFIG_H */ + +#if defined(_MSC_VER) +# pragma warning(push,1) +#endif + +/* ================================================================ */ +/* Definition of preprocessor macros/symbols which modify compiler */ +/* behavior or generated code characteristics must be done here, */ +/* as appropriate, before any system header file is included. It is */ +/* also possible to have them defined in the config file included */ +/* before this point. As a result of all this we frown inclusion of */ +/* system header files in our config files, avoid this at any cost. */ +/* ================================================================ */ + +/* + * AIX 4.3 and newer needs _THREAD_SAFE defined to build + * proper reentrant code. Others may also need it. + */ + +#ifdef NEED_THREAD_SAFE +# ifndef _THREAD_SAFE +# define _THREAD_SAFE +# endif +#endif + +/* + * Tru64 needs _REENTRANT set for a few function prototypes and + * things to appear in the system header files. Unixware needs it + * to build proper reentrant code. Others may also need it. + */ + +#ifdef NEED_REENTRANT +# ifndef _REENTRANT +# define _REENTRANT +# endif +#endif + +/* Solaris needs this to get a POSIX-conformant getpwuid_r */ +#if defined(sun) || defined(__sun) +# ifndef _POSIX_PTHREAD_SEMANTICS +# define _POSIX_PTHREAD_SEMANTICS 1 +# endif +#endif + +/* ================================================================ */ +/* If you need to include a system header file for your platform, */ +/* please, do it beyond the point further indicated in this file. */ +/* ================================================================ */ + +/* + * Disable other protocols when http is the only one desired. + */ + +#ifdef HTTP_ONLY +# ifndef CURL_DISABLE_DICT +# define CURL_DISABLE_DICT +# endif +# ifndef CURL_DISABLE_FILE +# define CURL_DISABLE_FILE +# endif +# ifndef CURL_DISABLE_FTP +# define CURL_DISABLE_FTP +# endif +# ifndef CURL_DISABLE_GOPHER +# define CURL_DISABLE_GOPHER +# endif +# ifndef CURL_DISABLE_IMAP +# define CURL_DISABLE_IMAP +# endif +# ifndef CURL_DISABLE_LDAP +# define CURL_DISABLE_LDAP +# endif +# ifndef CURL_DISABLE_LDAPS +# define CURL_DISABLE_LDAPS +# endif +# ifndef CURL_DISABLE_MQTT +# define CURL_DISABLE_MQTT +# endif +# ifndef CURL_DISABLE_POP3 +# define CURL_DISABLE_POP3 +# endif +# ifndef CURL_DISABLE_RTSP +# define CURL_DISABLE_RTSP +# endif +# ifndef CURL_DISABLE_SMB +# define CURL_DISABLE_SMB +# endif +# ifndef CURL_DISABLE_SMTP +# define CURL_DISABLE_SMTP +# endif +# ifndef CURL_DISABLE_TELNET +# define CURL_DISABLE_TELNET +# endif +# ifndef CURL_DISABLE_TFTP +# define CURL_DISABLE_TFTP +# 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 + */ + +#if defined(CURL_DISABLE_HTTP) +# define CURL_DISABLE_ALTSVC 1 +# define CURL_DISABLE_COOKIES 1 +# define CURL_DISABLE_BASIC_AUTH 1 +# define CURL_DISABLE_BEARER_AUTH 1 +# define CURL_DISABLE_AWS 1 +# define CURL_DISABLE_DOH 1 +# define CURL_DISABLE_FORM_API 1 +# define CURL_DISABLE_HEADERS_API 1 +# define CURL_DISABLE_HSTS 1 +# define CURL_DISABLE_HTTP_AUTH 1 +#endif + +/* ================================================================ */ +/* No system header file shall be included in this file before this */ +/* point. */ +/* ================================================================ */ + +/* + * OS/400 setup file includes some system headers. + */ + +#ifdef __OS400__ +# include "setup-os400.h" +#endif + +/* + * VMS setup file includes some system headers. + */ + +#ifdef __VMS +# include "setup-vms.h" +#endif + +/* + * Windows setup file includes some system headers. + */ + +#ifdef HAVE_WINDOWS_H +# include "setup-win32.h" +#endif + +#include <curl/system.h> + +/* + * Use getaddrinfo to resolve the IPv4 address literal. If the current network + * interface doesn't support IPv4, but supports IPv6, NAT64, and DNS64, + * performing this task will result in a synthesized IPv6 address. + */ +#if defined(__APPLE__) && !defined(USE_ARES) +#include <TargetConditionals.h> +#define USE_RESOLVE_ON_IPS 1 +# if TARGET_OS_MAC && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) && \ + defined(ENABLE_IPV6) +# define CURL_MACOS_CALL_COPYPROXIES 1 +# endif +#endif + +#ifdef USE_LWIPSOCK +# include <lwip/init.h> +# include <lwip/sockets.h> +# include <lwip/netdb.h> +#endif + +#ifdef HAVE_EXTRA_STRICMP_H +# include <extra/stricmp.h> +#endif + +#ifdef HAVE_EXTRA_STRDUP_H +# include <extra/strdup.h> +#endif + +#ifdef __AMIGA__ +# ifdef __amigaos4__ +# define __USE_INLINE__ + /* use our own resolver which uses runtime feature detection */ +# define CURLRES_AMIGA + /* getaddrinfo() currently crashes bsdsocket.library, so disable */ +# undef HAVE_GETADDRINFO +# if !(defined(__NEWLIB__) || \ + (defined(__CLIB2__) && defined(__THREAD_SAFE))) + /* disable threaded resolver with clib2 - requires newlib or clib-ts */ +# undef USE_THREADS_POSIX +# endif +# endif +# include <exec/types.h> +# include <exec/execbase.h> +# include <proto/exec.h> +# include <proto/dos.h> +# include <unistd.h> +# if defined(HAVE_PROTO_BSDSOCKET_H) && \ + (!defined(__amigaos4__) || defined(USE_AMISSL)) + /* use bsdsocket.library directly, instead of libc networking functions */ +# define _SYS_MBUF_H /* m_len define clashes with curl */ +# include <proto/bsdsocket.h> +# ifdef __amigaos4__ + int Curl_amiga_select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *errorfds, struct timeval *timeout); +# define select(a,b,c,d,e) Curl_amiga_select(a,b,c,d,e) +# else +# define select(a,b,c,d,e) WaitSelect(a,b,c,d,e,0) +# endif + /* must not use libc's fcntl() on bsdsocket.library sockfds! */ +# undef HAVE_FCNTL +# undef HAVE_FCNTL_O_NONBLOCK +# else + /* use libc networking and hence close() and fnctl() */ +# undef HAVE_CLOSESOCKET_CAMEL +# undef HAVE_IOCTLSOCKET_CAMEL +# endif +/* + * In clib2 arpa/inet.h warns that some prototypes may clash + * with bsdsocket.library. This avoids the definition of those. + */ +# define __NO_NET_API +#endif + +#include <stdio.h> +#include <assert.h> + +#ifdef __TANDEM /* for ns*-tandem-nsk systems */ +# if ! defined __LP64 +# include <floss.h> /* FLOSS is only used for 32-bit builds. */ +# endif +#endif + +#ifndef STDC_HEADERS /* no standard C headers! */ +#include <curl/stdcheaders.h> +#endif + +/* Default Windows file API selection. */ +#ifdef _WIN32 +# if defined(_MSC_VER) && (_INTEGRAL_MAX_BITS >= 64) +# define USE_WIN32_LARGE_FILES +# elif defined(__MINGW32__) +# define USE_WIN32_LARGE_FILES +# else +# define USE_WIN32_SMALL_FILES +# endif +#endif + +/* + * Large file (>2Gb) support using WIN32 functions. + */ + +#ifdef USE_WIN32_LARGE_FILES +# include <io.h> +# include <sys/types.h> +# include <sys/stat.h> +# undef lseek +# define lseek(fdes,offset,whence) _lseeki64(fdes, offset, whence) +# undef fstat +# define fstat(fdes,stp) _fstati64(fdes, stp) +# undef stat +# define stat(fname,stp) curlx_win32_stat(fname, stp) +# define struct_stat struct _stati64 +# define LSEEK_ERROR (__int64)-1 +# define open curlx_win32_open +# define fopen(fname,mode) curlx_win32_fopen(fname, mode) +# define access(fname,mode) curlx_win32_access(fname, mode) + int curlx_win32_open(const char *filename, int oflag, ...); + int curlx_win32_stat(const char *path, struct_stat *buffer); + FILE *curlx_win32_fopen(const char *filename, const char *mode); + int curlx_win32_access(const char *path, int mode); +#endif + +/* + * Small file (<2Gb) support using WIN32 functions. + */ + +#ifdef USE_WIN32_SMALL_FILES +# include <io.h> +# include <sys/types.h> +# include <sys/stat.h> +# ifndef _WIN32_WCE +# undef lseek +# define lseek(fdes,offset,whence) _lseek(fdes, (long)offset, whence) +# define fstat(fdes,stp) _fstat(fdes, stp) +# define stat(fname,stp) curlx_win32_stat(fname, stp) +# define struct_stat struct _stat +# define open curlx_win32_open +# define fopen(fname,mode) curlx_win32_fopen(fname, mode) +# define access(fname,mode) curlx_win32_access(fname, mode) + int curlx_win32_stat(const char *path, struct_stat *buffer); + int curlx_win32_open(const char *filename, int oflag, ...); + FILE *curlx_win32_fopen(const char *filename, const char *mode); + int curlx_win32_access(const char *path, int mode); +# endif +# define LSEEK_ERROR (long)-1 +#endif + +#ifndef struct_stat +# define struct_stat struct stat +#endif + +#ifndef LSEEK_ERROR +# define LSEEK_ERROR (off_t)-1 +#endif + +#ifndef SIZEOF_TIME_T +/* assume default size of time_t to be 32 bit */ +#define SIZEOF_TIME_T 4 +#endif + +/* + * Default sizeof(off_t) in case it hasn't been defined in config file. + */ + +#ifndef SIZEOF_OFF_T +# if defined(__VMS) && !defined(__VAX) +# if defined(_LARGEFILE) +# define SIZEOF_OFF_T 8 +# endif +# elif defined(__OS400__) && defined(__ILEC400__) +# if defined(_LARGE_FILES) +# define SIZEOF_OFF_T 8 +# endif +# elif defined(__MVS__) && defined(__IBMC__) +# if defined(_LP64) || defined(_LARGE_FILES) +# define SIZEOF_OFF_T 8 +# endif +# elif defined(__370__) && defined(__IBMC__) +# if defined(_LP64) || defined(_LARGE_FILES) +# define SIZEOF_OFF_T 8 +# endif +# endif +# ifndef SIZEOF_OFF_T +# define SIZEOF_OFF_T 4 +# endif +#endif + +#if (SIZEOF_CURL_OFF_T < 8) +#error "too small curl_off_t" +#else + /* assume SIZEOF_CURL_OFF_T == 8 */ +# define CURL_OFF_T_MAX CURL_OFF_T_C(0x7FFFFFFFFFFFFFFF) +#endif +#define CURL_OFF_T_MIN (-CURL_OFF_T_MAX - CURL_OFF_T_C(1)) + +#if (SIZEOF_TIME_T == 4) +# ifdef HAVE_TIME_T_UNSIGNED +# define TIME_T_MAX UINT_MAX +# define TIME_T_MIN 0 +# else +# define TIME_T_MAX INT_MAX +# define TIME_T_MIN INT_MIN +# endif +#else +# ifdef HAVE_TIME_T_UNSIGNED +# define TIME_T_MAX 0xFFFFFFFFFFFFFFFF +# define TIME_T_MIN 0 +# else +# define TIME_T_MAX 0x7FFFFFFFFFFFFFFF +# define TIME_T_MIN (-TIME_T_MAX - 1) +# endif +#endif + +#ifndef SIZE_T_MAX +/* some limits.h headers have this defined, some don't */ +#if defined(SIZEOF_SIZE_T) && (SIZEOF_SIZE_T > 4) +#define SIZE_T_MAX 18446744073709551615U +#else +#define SIZE_T_MAX 4294967295U +#endif +#endif + +#ifndef SSIZE_T_MAX +/* some limits.h headers have this defined, some don't */ +#if defined(SIZEOF_SIZE_T) && (SIZEOF_SIZE_T > 4) +#define SSIZE_T_MAX 9223372036854775807 +#else +#define SSIZE_T_MAX 2147483647 +#endif +#endif + +/* + * Arg 2 type for gethostname in case it hasn't been defined in config file. + */ + +#ifndef GETHOSTNAME_TYPE_ARG2 +# ifdef USE_WINSOCK +# define GETHOSTNAME_TYPE_ARG2 int +# else +# define GETHOSTNAME_TYPE_ARG2 size_t +# endif +#endif + +/* Below we define some functions. They should + + 4. set the SIGALRM signal timeout + 5. set dir/file naming defines + */ + +#ifdef _WIN32 + +# define DIR_CHAR "\\" + +#else /* _WIN32 */ + +# ifdef MSDOS /* Watt-32 */ + +# include <sys/ioctl.h> +# define select(n,r,w,x,t) select_s(n,r,w,x,t) +# define ioctl(x,y,z) ioctlsocket(x,y,(char *)(z)) +# include <tcp.h> +# ifdef word +# undef word +# endif +# ifdef byte +# undef byte +# endif + +# endif /* MSDOS */ + +# ifdef __minix + /* Minix 3 versions up to at least 3.1.3 are missing these prototypes */ + extern char *strtok_r(char *s, const char *delim, char **last); + extern struct tm *gmtime_r(const time_t * const timep, struct tm *tmp); +# endif + +# define DIR_CHAR "/" + +#endif /* _WIN32 */ + +/* ---------------------------------------------------------------- */ +/* resolver specialty compile-time defines */ +/* CURLRES_* defines to use in the host*.c sources */ +/* ---------------------------------------------------------------- */ + +/* + * MSVC threads support requires a multi-threaded runtime library. + * _beginthreadex() is not available in single-threaded ones. + */ + +#if defined(_MSC_VER) && !defined(_MT) +# undef USE_THREADS_POSIX +# undef USE_THREADS_WIN32 +#endif + +/* + * Mutually exclusive CURLRES_* definitions. + */ + +#if defined(ENABLE_IPV6) && defined(HAVE_GETADDRINFO) +# define CURLRES_IPV6 +#elif defined(ENABLE_IPV6) && (defined(_WIN32) || defined(__CYGWIN__)) +/* assume on Windows that IPv6 without getaddrinfo is a broken build */ +# error "Unexpected build: IPv6 is enabled but getaddrinfo was not found." +#else +# define CURLRES_IPV4 +#endif + +#ifdef USE_ARES +# define CURLRES_ASYNCH +# define CURLRES_ARES +/* now undef the stock libc functions just to avoid them being used */ +# undef HAVE_GETADDRINFO +# undef HAVE_FREEADDRINFO +#elif defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) +# define CURLRES_ASYNCH +# define CURLRES_THREADED +#else +# define CURLRES_SYNCH +#endif + +/* ---------------------------------------------------------------- */ + +#if defined(HAVE_LIBIDN2) && defined(HAVE_IDN2_H) && !defined(USE_WIN32_IDN) +/* The lib and header are present */ +#define USE_LIBIDN2 +#endif + +#if defined(USE_LIBIDN2) && defined(USE_WIN32_IDN) +#error "Both libidn2 and WinIDN are enabled, choose one." +#endif + +#define LIBIDN_REQUIRED_VERSION "0.4.1" + +#if defined(USE_GNUTLS) || defined(USE_OPENSSL) || defined(USE_MBEDTLS) || \ + defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) +#define USE_SSL /* SSL support has been enabled */ +#endif + +/* Single point where USE_SPNEGO definition might be defined */ +#if !defined(CURL_DISABLE_NEGOTIATE_AUTH) && \ + (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)) +#define USE_SPNEGO +#endif + +/* Single point where USE_KERBEROS5 definition might be defined */ +#if !defined(CURL_DISABLE_KERBEROS_AUTH) && \ + (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)) +#define USE_KERBEROS5 +#endif + +/* Single point where USE_NTLM definition might be defined */ +#if !defined(CURL_DISABLE_NTLM) +# if defined(USE_OPENSSL) || defined(USE_MBEDTLS) || \ + defined(USE_GNUTLS) || defined(USE_SECTRANSP) || \ + defined(USE_OS400CRYPTO) || defined(USE_WIN32_CRYPTO) || \ + (defined(USE_WOLFSSL) && defined(HAVE_WOLFSSL_DES_ECB_ENCRYPT)) +# define USE_CURL_NTLM_CORE +# endif +# if defined(USE_CURL_NTLM_CORE) || defined(USE_WINDOWS_SSPI) +# define USE_NTLM +# endif +#endif + +#ifdef CURL_WANTS_CA_BUNDLE_ENV +#error "No longer supported. Set CURLOPT_CAINFO at runtime instead." +#endif + +#if defined(USE_LIBSSH2) || defined(USE_LIBSSH) || defined(USE_WOLFSSH) +#define USE_SSH +#endif + +/* + * Provide a mechanism to silence picky compilers, such as gcc 4.6+. + * Parameters should of course normally not be unused, but for example when + * we have multiple implementations of the same interface it may happen. + */ + +#if defined(__GNUC__) && ((__GNUC__ >= 3) || \ + ((__GNUC__ == 2) && defined(__GNUC_MINOR__) && (__GNUC_MINOR__ >= 7))) +# define UNUSED_PARAM __attribute__((__unused__)) +# define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +# define UNUSED_PARAM /* NOTHING */ +# define WARN_UNUSED_RESULT +#endif + +/* noreturn attribute */ + +#if !defined(CURL_NORETURN) +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) +# define CURL_NORETURN __attribute__((__noreturn__)) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) +# define CURL_NORETURN __declspec(noreturn) +#else +# define CURL_NORETURN +#endif +#endif + +/* + * Include macros and defines that should only be processed once. + */ + +#ifndef HEADER_CURL_SETUP_ONCE_H +#include "curl_setup_once.h" +#endif + +/* + * Definition of our NOP statement Object-like macro + */ + +#ifndef Curl_nop_stmt +# define Curl_nop_stmt do { } while(0) +#endif + +/* + * Ensure that Winsock and lwIP TCP/IP stacks are not mixed. + */ + +#if defined(__LWIP_OPT_H__) || defined(LWIP_HDR_OPT_H) +# if defined(SOCKET) || \ + defined(USE_WINSOCK) || \ + defined(HAVE_WINSOCK2_H) || \ + defined(HAVE_WS2TCPIP_H) +# error "WinSock and lwIP TCP/IP stack definitions shall not coexist!" +# endif +#endif + +/* + * shutdown() flags for systems that don't define them + */ + +#ifndef SHUT_RD +#define SHUT_RD 0x00 +#endif + +#ifndef SHUT_WR +#define SHUT_WR 0x01 +#endif + +#ifndef SHUT_RDWR +#define SHUT_RDWR 0x02 +#endif + +/* Define S_ISREG if not defined by system headers, e.g. MSVC */ +#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif + +/* Define S_ISDIR if not defined by system headers, e.g. MSVC */ +#if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#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 +*/ +#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. +*/ +#define FOPEN_READTEXT "rt" +#define FOPEN_WRITETEXT "w" +#define FOPEN_APPENDTEXT "a" +#else +#define FOPEN_READTEXT "r" +#define FOPEN_WRITETEXT "w" +#define FOPEN_APPENDTEXT "a" +#endif + +/* for systems that don't detect this in configure */ +#ifndef CURL_SA_FAMILY_T +# if defined(HAVE_SA_FAMILY_T) +# define CURL_SA_FAMILY_T sa_family_t +# elif defined(HAVE_ADDRESS_FAMILY) +# define CURL_SA_FAMILY_T ADDRESS_FAMILY +# else +/* use a sensible default */ +# define CURL_SA_FAMILY_T unsigned short +# endif +#endif + +/* Some convenience macros to get the larger/smaller value out of two given. + We prefix with CURL to prevent name collisions. */ +#define CURLMAX(x,y) ((x)>(y)?(x):(y)) +#define CURLMIN(x,y) ((x)<(y)?(x):(y)) + +/* 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 + +/* Some versions of the Android SDK is missing the declaration */ +#if defined(HAVE_GETPWUID_R) && defined(HAVE_DECL_GETPWUID_R_MISSING) +struct passwd; +int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, + size_t buflen, struct passwd **result); +#endif + +#ifdef DEBUGBUILD +#define UNITTEST +#else +#define UNITTEST static +#endif + +/* Hyper supports HTTP2 also, but Curl's integration with Hyper does not */ +#if defined(USE_NGHTTP2) +#define USE_HTTP2 +#endif + +#if (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \ + defined(USE_QUICHE) || defined(USE_MSH3) +#define ENABLE_QUIC +#define USE_HTTP3 +#endif + +/* Certain Windows implementations are not aligned with what curl expects, + so always use the local one on this platform. E.g. the mingw-w64 + implementation can return wrong results for non-ASCII inputs. */ +#if defined(HAVE_BASENAME) && defined(_WIN32) +#undef HAVE_BASENAME +#endif + +#if defined(USE_UNIX_SOCKETS) && defined(_WIN32) +# if !defined(UNIX_PATH_MAX) + /* Replicating logic present in afunix.h + (distributed with newer Windows 10 SDK versions only) */ +# define UNIX_PATH_MAX 108 + /* !checksrc! disable TYPEDEFSTRUCT 1 */ + typedef struct sockaddr_un { + ADDRESS_FAMILY sun_family; + char sun_path[UNIX_PATH_MAX]; + } SOCKADDR_UN, *PSOCKADDR_UN; +# define WIN32_SOCKADDR_UN +# endif +#endif + +/* OpenSSLv3 marks DES, MD5 and ENGINE functions deprecated but we have no + replacements (yet) so tell the compiler to not warn for them. */ +#ifdef USE_OPENSSL +#define OPENSSL_SUPPRESS_DEPRECATED +#endif + +#endif /* HEADER_CURL_SETUP_H */ diff --git a/Utilities/cmcurl/lib/curl_setup_once.h b/Utilities/cmcurl/lib/curl_setup_once.h new file mode 100644 index 0000000..bf0ee66 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_setup_once.h @@ -0,0 +1,418 @@ +#ifndef HEADER_CURL_SETUP_ONCE_H +#define HEADER_CURL_SETUP_ONCE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + + +/* + * Inclusion of common header files. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> +#include <errno.h> + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#ifdef NEED_MALLOC_H +#include <malloc.h> +#endif + +#ifdef NEED_MEMORY_H +#include <memory.h> +#endif + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifdef _WIN32 +#include <io.h> +#include <fcntl.h> +#endif + +#if defined(HAVE_STDBOOL_H) && defined(HAVE_BOOL_T) +#include <stdbool.h> +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef USE_WOLFSSL +#include <stdint.h> +#endif + +#ifdef USE_SCHANNEL +/* Must set this before <schannel.h> is included directly or indirectly by + another Windows header. */ +# define SCHANNEL_USE_BLACKLISTS 1 +#endif + +#ifdef __hpux +# if !defined(_XOPEN_SOURCE_EXTENDED) || defined(_KERNEL) +# ifdef _APP32_64BIT_OFF_T +# define OLD_APP32_64BIT_OFF_T _APP32_64BIT_OFF_T +# undef _APP32_64BIT_OFF_T +# else +# undef OLD_APP32_64BIT_OFF_T +# endif +# endif +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#include "functypes.h" + +#ifdef __hpux +# if !defined(_XOPEN_SOURCE_EXTENDED) || defined(_KERNEL) +# ifdef OLD_APP32_64BIT_OFF_T +# define _APP32_64BIT_OFF_T OLD_APP32_64BIT_OFF_T +# undef OLD_APP32_64BIT_OFF_T +# endif +# endif +#endif + +/* + * Definition of timeval struct for platforms that don't have it. + */ + +#ifndef HAVE_STRUCT_TIMEVAL +struct timeval { + long tv_sec; + long tv_usec; +}; +#endif + + +/* + * If we have the MSG_NOSIGNAL define, make sure we use + * it as the fourth argument of function send() + */ + +#ifdef HAVE_MSG_NOSIGNAL +#define SEND_4TH_ARG MSG_NOSIGNAL +#else +#define SEND_4TH_ARG 0 +#endif + + +#if defined(__minix) +/* Minix doesn't support recv on TCP sockets */ +#define sread(x,y,z) (ssize_t)read((RECV_TYPE_ARG1)(x), \ + (RECV_TYPE_ARG2)(y), \ + (RECV_TYPE_ARG3)(z)) + +#elif defined(HAVE_RECV) +/* + * The definitions for the return type and arguments types + * of functions recv() and send() belong and come from the + * configuration file. Do not define them in any other place. + * + * HAVE_RECV is defined if you have a function named recv() + * which is used to read incoming data from sockets. If your + * function has another name then don't define HAVE_RECV. + * + * If HAVE_RECV is defined then RECV_TYPE_ARG1, RECV_TYPE_ARG2, + * RECV_TYPE_ARG3, RECV_TYPE_ARG4 and RECV_TYPE_RETV must also + * be defined. + * + * HAVE_SEND is defined if you have a function named send() + * which is used to write outgoing data on a connected socket. + * If yours has another name then don't define HAVE_SEND. + * + * If HAVE_SEND is defined then SEND_TYPE_ARG1, SEND_QUAL_ARG2, + * SEND_TYPE_ARG2, SEND_TYPE_ARG3, SEND_TYPE_ARG4 and + * SEND_TYPE_RETV must also be defined. + */ + +#define sread(x,y,z) (ssize_t)recv((RECV_TYPE_ARG1)(x), \ + (RECV_TYPE_ARG2)(y), \ + (RECV_TYPE_ARG3)(z), \ + (RECV_TYPE_ARG4)(0)) +#else /* HAVE_RECV */ +#ifndef sread + /* */ + Error Missing_definition_of_macro_sread + /* */ +#endif +#endif /* HAVE_RECV */ + + +#if defined(__minix) +/* Minix doesn't support send on TCP sockets */ +#define swrite(x,y,z) (ssize_t)write((SEND_TYPE_ARG1)(x), \ + (SEND_TYPE_ARG2)(y), \ + (SEND_TYPE_ARG3)(z)) + +#elif defined(HAVE_SEND) +#define swrite(x,y,z) (ssize_t)send((SEND_TYPE_ARG1)(x), \ + (SEND_QUAL_ARG2 SEND_TYPE_ARG2)(y), \ + (SEND_TYPE_ARG3)(z), \ + (SEND_TYPE_ARG4)(SEND_4TH_ARG)) +#else /* HAVE_SEND */ +#ifndef swrite + /* */ + Error Missing_definition_of_macro_swrite + /* */ +#endif +#endif /* HAVE_SEND */ + + +/* + * Function-like macro definition used to close a socket. + */ + +#if defined(HAVE_CLOSESOCKET) +# define sclose(x) closesocket((x)) +#elif defined(HAVE_CLOSESOCKET_CAMEL) +# define sclose(x) CloseSocket((x)) +#elif defined(HAVE_CLOSE_S) +# define sclose(x) close_s((x)) +#elif defined(USE_LWIPSOCK) +# define sclose(x) lwip_close((x)) +#else +# define sclose(x) close((x)) +#endif + +/* + * Stack-independent version of fcntl() on sockets: + */ +#if defined(USE_LWIPSOCK) +# define sfcntl lwip_fcntl +#else +# define sfcntl fcntl +#endif + +/* + * 'bool' stuff compatible with HP-UX headers. + */ + +#if defined(__hpux) && !defined(HAVE_BOOL_T) + typedef int bool; +# define false 0 +# define true 1 +# define HAVE_BOOL_T +#endif + + +/* + * 'bool' exists on platforms with <stdbool.h>, i.e. C99 platforms. + * On non-C99 platforms there's no bool, so define an enum for that. + * On C99 platforms 'false' and 'true' also exist. Enum uses a + * global namespace though, so use bool_false and bool_true. + */ + +#ifndef HAVE_BOOL_T + typedef enum { + bool_false = 0, + bool_true = 1 + } bool; + +/* + * Use a define to let 'true' and 'false' use those enums. There + * are currently no use of true and false in libcurl proper, but + * there are some in the examples. This will cater for any later + * code happening to use true and false. + */ +# define false bool_false +# define true bool_true +# define HAVE_BOOL_T +#endif + +/* the type we use for storing a single boolean bit */ +#ifdef _MSC_VER +typedef bool bit; +#define BIT(x) bool x +#else +typedef unsigned int bit; +#define BIT(x) bit x:1 +#endif + +/* + * Redefine TRUE and FALSE too, to catch current use. With this + * change, 'bool found = 1' will give a warning on MIPSPro, but + * 'bool found = TRUE' will not. Change tested on IRIX/MIPSPro, + * AIX 5.1/Xlc, Tru64 5.1/cc, w/make test too. + */ + +#ifndef TRUE +#define TRUE true +#endif +#ifndef FALSE +#define FALSE false +#endif + +#include "curl_ctype.h" + + +/* + * Macro used to include code only in debug builds. + */ + +#ifdef DEBUGBUILD +#define DEBUGF(x) x +#else +#define DEBUGF(x) do { } while(0) +#endif + + +/* + * Macro used to include assertion code only in debug builds. + */ + +#undef DEBUGASSERT +#if defined(DEBUGBUILD) +#define DEBUGASSERT(x) assert(x) +#else +#define DEBUGASSERT(x) do { } while(0) +#endif + + +/* + * Macro SOCKERRNO / SET_SOCKERRNO() returns / sets the *socket-related* errno + * (or equivalent) on this platform to hide platform details to code using it. + */ + +#ifdef USE_WINSOCK +#define SOCKERRNO ((int)WSAGetLastError()) +#define SET_SOCKERRNO(x) (WSASetLastError((int)(x))) +#else +#define SOCKERRNO (errno) +#define SET_SOCKERRNO(x) (errno = (x)) +#endif + + +/* + * Portable error number symbolic names defined to Winsock error codes. + */ + +#ifdef USE_WINSOCK +#undef EBADF /* override definition in errno.h */ +#define EBADF WSAEBADF +#undef EINTR /* override definition in errno.h */ +#define EINTR WSAEINTR +#undef EINVAL /* override definition in errno.h */ +#define EINVAL WSAEINVAL +#undef EWOULDBLOCK /* override definition in errno.h */ +#define EWOULDBLOCK WSAEWOULDBLOCK +#undef EINPROGRESS /* override definition in errno.h */ +#define EINPROGRESS WSAEINPROGRESS +#undef EALREADY /* override definition in errno.h */ +#define EALREADY WSAEALREADY +#undef ENOTSOCK /* override definition in errno.h */ +#define ENOTSOCK WSAENOTSOCK +#undef EDESTADDRREQ /* override definition in errno.h */ +#define EDESTADDRREQ WSAEDESTADDRREQ +#undef EMSGSIZE /* override definition in errno.h */ +#define EMSGSIZE WSAEMSGSIZE +#undef EPROTOTYPE /* override definition in errno.h */ +#define EPROTOTYPE WSAEPROTOTYPE +#undef ENOPROTOOPT /* override definition in errno.h */ +#define ENOPROTOOPT WSAENOPROTOOPT +#undef EPROTONOSUPPORT /* override definition in errno.h */ +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT +#undef EOPNOTSUPP /* override definition in errno.h */ +#define EOPNOTSUPP WSAEOPNOTSUPP +#define EPFNOSUPPORT WSAEPFNOSUPPORT +#undef EAFNOSUPPORT /* override definition in errno.h */ +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#undef EADDRINUSE /* override definition in errno.h */ +#define EADDRINUSE WSAEADDRINUSE +#undef EADDRNOTAVAIL /* override definition in errno.h */ +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#undef ENETDOWN /* override definition in errno.h */ +#define ENETDOWN WSAENETDOWN +#undef ENETUNREACH /* override definition in errno.h */ +#define ENETUNREACH WSAENETUNREACH +#undef ENETRESET /* override definition in errno.h */ +#define ENETRESET WSAENETRESET +#undef ECONNABORTED /* override definition in errno.h */ +#define ECONNABORTED WSAECONNABORTED +#undef ECONNRESET /* override definition in errno.h */ +#define ECONNRESET WSAECONNRESET +#undef ENOBUFS /* override definition in errno.h */ +#define ENOBUFS WSAENOBUFS +#undef EISCONN /* override definition in errno.h */ +#define EISCONN WSAEISCONN +#undef ENOTCONN /* override definition in errno.h */ +#define ENOTCONN WSAENOTCONN +#define ESHUTDOWN WSAESHUTDOWN +#define ETOOMANYREFS WSAETOOMANYREFS +#undef ETIMEDOUT /* override definition in errno.h */ +#define ETIMEDOUT WSAETIMEDOUT +#undef ECONNREFUSED /* override definition in errno.h */ +#define ECONNREFUSED WSAECONNREFUSED +#undef ELOOP /* override definition in errno.h */ +#define ELOOP WSAELOOP +#ifndef ENAMETOOLONG /* possible previous definition in errno.h */ +#define ENAMETOOLONG WSAENAMETOOLONG +#endif +#define EHOSTDOWN WSAEHOSTDOWN +#undef EHOSTUNREACH /* override definition in errno.h */ +#define EHOSTUNREACH WSAEHOSTUNREACH +#ifndef ENOTEMPTY /* possible previous definition in errno.h */ +#define ENOTEMPTY WSAENOTEMPTY +#endif +#define EPROCLIM WSAEPROCLIM +#define EUSERS WSAEUSERS +#define EDQUOT WSAEDQUOT +#define ESTALE WSAESTALE +#define EREMOTE WSAEREMOTE +#endif + +/* + * Macro argv_item_t hides platform details to code using it. + */ + +#ifdef __VMS +#define argv_item_t __char_ptr32 +#elif defined(_UNICODE) +#define argv_item_t wchar_t * +#else +#define argv_item_t char * +#endif + + +/* + * We use this ZERO_NULL to avoid picky compiler warnings, + * when assigning a NULL pointer to a function pointer var. + */ + +#define ZERO_NULL 0 + + +#endif /* HEADER_CURL_SETUP_ONCE_H */ diff --git a/Utilities/cmcurl/lib/curl_sha256.h b/Utilities/cmcurl/lib/curl_sha256.h new file mode 100644 index 0000000..d99f958 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_sha256.h @@ -0,0 +1,50 @@ +#ifndef HEADER_CURL_SHA256_H +#define HEADER_CURL_SHA256_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Florin Petriuc, <petriuc.florin@gmail.com> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) \ + || defined(USE_LIBSSH2) + +#include <curl/curl.h> +#include "curl_hmac.h" + +extern const struct HMAC_params Curl_HMAC_SHA256[1]; + +#ifdef USE_WOLFSSL +/* SHA256_DIGEST_LENGTH is an enum value in wolfSSL. Need to import it from + * sha.h */ +#include <wolfssl/options.h> +#include <wolfssl/openssl/sha.h> +#else +#define SHA256_DIGEST_LENGTH 32 +#endif + +CURLcode Curl_sha256it(unsigned char *outbuffer, const unsigned char *input, + const size_t len); + +#endif + +#endif /* HEADER_CURL_SHA256_H */ diff --git a/Utilities/cmcurl/lib/curl_sspi.c b/Utilities/cmcurl/lib/curl_sspi.c new file mode 100644 index 0000000..eb21e7e --- /dev/null +++ b/Utilities/cmcurl/lib/curl_sspi.c @@ -0,0 +1,239 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_WINDOWS_SSPI + +#include <curl/curl.h> +#include "curl_sspi.h" +#include "curl_multibyte.h" +#include "system_win32.h" +#include "version_win32.h" +#include "warnless.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* We use our own typedef here since some headers might lack these */ +typedef PSecurityFunctionTable (APIENTRY *INITSECURITYINTERFACE_FN)(VOID); + +/* See definition of SECURITY_ENTRYPOINT in sspi.h */ +#ifdef UNICODE +# ifdef _WIN32_WCE +# define SECURITYENTRYPOINT L"InitSecurityInterfaceW" +# else +# define SECURITYENTRYPOINT "InitSecurityInterfaceW" +# endif +#else +# define SECURITYENTRYPOINT "InitSecurityInterfaceA" +#endif + +/* Handle of security.dll or secur32.dll, depending on Windows version */ +HMODULE s_hSecDll = NULL; + +/* Pointer to SSPI dispatch table */ +PSecurityFunctionTable s_pSecFn = NULL; + +/* + * Curl_sspi_global_init() + * + * This is used to load the Security Service Provider Interface (SSPI) + * dynamic link library portably across all Windows versions, without + * the need to directly link libcurl, nor the application using it, at + * build time. + * + * Once this function has been executed, Windows SSPI functions can be + * called through the Security Service Provider Interface dispatch table. + * + * Parameters: + * + * None. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_sspi_global_init(void) +{ + INITSECURITYINTERFACE_FN pInitSecurityInterface; + + /* If security interface is not yet initialized try to do this */ + if(!s_hSecDll) { + /* Security Service Provider Interface (SSPI) functions are located in + * security.dll on WinNT 4.0 and in secur32.dll on Win9x. Win2K and XP + * have both these DLLs (security.dll forwards calls to secur32.dll) */ + + /* Load SSPI dll into the address space of the calling process */ + if(curlx_verify_windows_version(4, 0, 0, PLATFORM_WINNT, VERSION_EQUAL)) + s_hSecDll = Curl_load_library(TEXT("security.dll")); + else + s_hSecDll = Curl_load_library(TEXT("secur32.dll")); + if(!s_hSecDll) + return CURLE_FAILED_INIT; + + /* Get address of the InitSecurityInterfaceA function from the SSPI dll */ + pInitSecurityInterface = + CURLX_FUNCTION_CAST(INITSECURITYINTERFACE_FN, + (GetProcAddress(s_hSecDll, SECURITYENTRYPOINT))); + if(!pInitSecurityInterface) + return CURLE_FAILED_INIT; + + /* Get pointer to Security Service Provider Interface dispatch table */ + s_pSecFn = pInitSecurityInterface(); + if(!s_pSecFn) + return CURLE_FAILED_INIT; + } + + return CURLE_OK; +} + +/* + * Curl_sspi_global_cleanup() + * + * This deinitializes the Security Service Provider Interface from libcurl. + * + * Parameters: + * + * None. + */ +void Curl_sspi_global_cleanup(void) +{ + if(s_hSecDll) { + FreeLibrary(s_hSecDll); + s_hSecDll = NULL; + s_pSecFn = NULL; + } +} + +/* + * Curl_create_sspi_identity() + * + * This is used to populate a SSPI identity structure based on the supplied + * username and password. + * + * Parameters: + * + * userp [in] - The user name in the format User or Domain\User. + * passwdp [in] - The user's password. + * identity [in/out] - The identity structure. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, + SEC_WINNT_AUTH_IDENTITY *identity) +{ + xcharp_u useranddomain; + xcharp_u user, dup_user; + xcharp_u domain, dup_domain; + xcharp_u passwd, dup_passwd; + size_t domlen = 0; + + domain.const_tchar_ptr = TEXT(""); + + /* Initialize the identity */ + memset(identity, 0, sizeof(*identity)); + + useranddomain.tchar_ptr = curlx_convert_UTF8_to_tchar((char *)userp); + if(!useranddomain.tchar_ptr) + return CURLE_OUT_OF_MEMORY; + + 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('/')); + + if(user.tchar_ptr) { + domain.tchar_ptr = useranddomain.tchar_ptr; + domlen = user.tchar_ptr - useranddomain.tchar_ptr; + user.tchar_ptr++; + } + else { + user.tchar_ptr = useranddomain.tchar_ptr; + domain.const_tchar_ptr = TEXT(""); + domlen = 0; + } + + /* Setup the identity's user and length */ + dup_user.tchar_ptr = _tcsdup(user.tchar_ptr); + if(!dup_user.tchar_ptr) { + curlx_unicodefree(useranddomain.tchar_ptr); + return CURLE_OUT_OF_MEMORY; + } + identity->User = dup_user.tbyte_ptr; + identity->UserLength = curlx_uztoul(_tcslen(dup_user.tchar_ptr)); + dup_user.tchar_ptr = NULL; + + /* Setup the identity's domain and length */ + dup_domain.tchar_ptr = malloc(sizeof(TCHAR) * (domlen + 1)); + if(!dup_domain.tchar_ptr) { + curlx_unicodefree(useranddomain.tchar_ptr); + return CURLE_OUT_OF_MEMORY; + } + _tcsncpy(dup_domain.tchar_ptr, domain.tchar_ptr, domlen); + *(dup_domain.tchar_ptr + domlen) = TEXT('\0'); + identity->Domain = dup_domain.tbyte_ptr; + identity->DomainLength = curlx_uztoul(domlen); + dup_domain.tchar_ptr = NULL; + + curlx_unicodefree(useranddomain.tchar_ptr); + + /* Setup the identity's password and length */ + passwd.tchar_ptr = curlx_convert_UTF8_to_tchar((char *)passwdp); + if(!passwd.tchar_ptr) + return CURLE_OUT_OF_MEMORY; + dup_passwd.tchar_ptr = _tcsdup(passwd.tchar_ptr); + if(!dup_passwd.tchar_ptr) { + curlx_unicodefree(passwd.tchar_ptr); + return CURLE_OUT_OF_MEMORY; + } + identity->Password = dup_passwd.tbyte_ptr; + identity->PasswordLength = curlx_uztoul(_tcslen(dup_passwd.tchar_ptr)); + dup_passwd.tchar_ptr = NULL; + + curlx_unicodefree(passwd.tchar_ptr); + + /* Setup the identity's flags */ + identity->Flags = SECFLAG_WINNT_AUTH_IDENTITY; + + return CURLE_OK; +} + +/* + * Curl_sspi_free_identity() + * + * This is used to free the contents of a SSPI identifier structure. + * + * Parameters: + * + * identity [in/out] - The identity structure. + */ +void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity) +{ + if(identity) { + Curl_safefree(identity->User); + Curl_safefree(identity->Password); + Curl_safefree(identity->Domain); + } +} + +#endif /* USE_WINDOWS_SSPI */ diff --git a/Utilities/cmcurl/lib/curl_sspi.h b/Utilities/cmcurl/lib/curl_sspi.h new file mode 100644 index 0000000..b26c391 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_sspi.h @@ -0,0 +1,123 @@ +#ifndef HEADER_CURL_SSPI_H +#define HEADER_CURL_SSPI_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_WINDOWS_SSPI + +#include <curl/curl.h> + +/* + * When including the following three headers, it is mandatory to define either + * SECURITY_WIN32 or SECURITY_KERNEL, indicating who is compiling the code. + */ + +#undef SECURITY_WIN32 +#undef SECURITY_KERNEL +#define SECURITY_WIN32 1 +#include <security.h> +#include <sspi.h> +#include <rpc.h> + +CURLcode Curl_sspi_global_init(void); +void Curl_sspi_global_cleanup(void); + +/* This is used to populate the domain in a SSPI identity structure */ +CURLcode Curl_override_sspi_http_realm(const char *chlg, + SEC_WINNT_AUTH_IDENTITY *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); + +/* This is used to free an SSPI identity structure */ +void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity); + +/* Forward-declaration of global variables defined in curl_sspi.c */ +extern HMODULE s_hSecDll; +extern PSecurityFunctionTable s_pSecFn; + +/* Provide some definitions missing in old headers */ +#define SP_NAME_DIGEST "WDigest" +#define SP_NAME_NTLM "NTLM" +#define SP_NAME_NEGOTIATE "Negotiate" +#define SP_NAME_KERBEROS "Kerberos" + +#ifndef ISC_REQ_USE_HTTP_STYLE +#define ISC_REQ_USE_HTTP_STYLE 0x01000000 +#endif + +#ifndef SEC_E_INVALID_PARAMETER +# define SEC_E_INVALID_PARAMETER ((HRESULT)0x8009035DL) +#endif +#ifndef SEC_E_DELEGATION_POLICY +# define SEC_E_DELEGATION_POLICY ((HRESULT)0x8009035EL) +#endif +#ifndef SEC_E_POLICY_NLTM_ONLY +# define SEC_E_POLICY_NLTM_ONLY ((HRESULT)0x8009035FL) +#endif + +#ifndef SEC_I_SIGNATURE_NEEDED +# define SEC_I_SIGNATURE_NEEDED ((HRESULT)0x0009035CL) +#endif + +#ifndef CRYPT_E_REVOKED +# define CRYPT_E_REVOKED ((HRESULT)0x80092010L) +#endif + +#ifndef CRYPT_E_NO_REVOCATION_DLL +# define CRYPT_E_NO_REVOCATION_DLL ((HRESULT)0x80092011L) +#endif + +#ifndef CRYPT_E_NO_REVOCATION_CHECK +# define CRYPT_E_NO_REVOCATION_CHECK ((HRESULT)0x80092012L) +#endif + +#ifndef CRYPT_E_REVOCATION_OFFLINE +# define CRYPT_E_REVOCATION_OFFLINE ((HRESULT)0x80092013L) +#endif + +#ifndef CRYPT_E_NOT_IN_REVOCATION_DATABASE +# define CRYPT_E_NOT_IN_REVOCATION_DATABASE ((HRESULT)0x80092014L) +#endif + +#ifdef UNICODE +# define SECFLAG_WINNT_AUTH_IDENTITY \ + (unsigned long)SEC_WINNT_AUTH_IDENTITY_UNICODE +#else +# define SECFLAG_WINNT_AUTH_IDENTITY \ + (unsigned long)SEC_WINNT_AUTH_IDENTITY_ANSI +#endif + +/* + * Definitions required from ntsecapi.h are directly provided below this point + * to avoid including ntsecapi.h due to a conflict with OpenSSL's safestack.h + */ +#define KERB_WRAP_NO_ENCRYPT 0x80000001 + +#endif /* USE_WINDOWS_SSPI */ + +#endif /* HEADER_CURL_SSPI_H */ diff --git a/Utilities/cmcurl/lib/curl_threads.c b/Utilities/cmcurl/lib/curl_threads.c new file mode 100644 index 0000000..222d936 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_threads.c @@ -0,0 +1,153 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#if defined(USE_THREADS_POSIX) +# ifdef HAVE_PTHREAD_H +# include <pthread.h> +# endif +#elif defined(USE_THREADS_WIN32) +# include <process.h> +#endif + +#include "curl_threads.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +#if defined(USE_THREADS_POSIX) + +struct Curl_actual_call { + unsigned int (*func)(void *); + void *arg; +}; + +static void *curl_thread_create_thunk(void *arg) +{ + struct Curl_actual_call *ac = arg; + unsigned int (*func)(void *) = ac->func; + void *real_arg = ac->arg; + + free(ac); + + (*func)(real_arg); + + return 0; +} + +curl_thread_t Curl_thread_create(unsigned int (*func) (void *), void *arg) +{ + curl_thread_t t = malloc(sizeof(pthread_t)); + struct Curl_actual_call *ac = malloc(sizeof(struct Curl_actual_call)); + if(!(ac && t)) + goto err; + + ac->func = func; + ac->arg = arg; + + if(pthread_create(t, NULL, curl_thread_create_thunk, ac) != 0) + goto err; + + return t; + +err: + free(t); + free(ac); + return curl_thread_t_null; +} + +void Curl_thread_destroy(curl_thread_t hnd) +{ + if(hnd != curl_thread_t_null) { + pthread_detach(*hnd); + free(hnd); + } +} + +int Curl_thread_join(curl_thread_t *hnd) +{ + int ret = (pthread_join(**hnd, NULL) == 0); + + free(*hnd); + *hnd = curl_thread_t_null; + + return ret; +} + +#elif defined(USE_THREADS_WIN32) + +/* !checksrc! disable SPACEBEFOREPAREN 1 */ +curl_thread_t Curl_thread_create(unsigned int (CURL_STDCALL *func) (void *), + void *arg) +{ +#ifdef _WIN32_WCE + typedef HANDLE curl_win_thread_handle_t; +#else + typedef uintptr_t curl_win_thread_handle_t; +#endif + curl_thread_t t; + curl_win_thread_handle_t thread_handle; +#ifdef _WIN32_WCE + thread_handle = CreateThread(NULL, 0, func, arg, 0, NULL); +#else + thread_handle = _beginthreadex(NULL, 0, func, arg, 0, NULL); +#endif + t = (curl_thread_t)thread_handle; + if((t == 0) || (t == LongToHandle(-1L))) { +#ifdef _WIN32_WCE + DWORD gle = GetLastError(); + errno = ((gle == ERROR_ACCESS_DENIED || + gle == ERROR_NOT_ENOUGH_MEMORY) ? + EACCES : EINVAL); +#endif + return curl_thread_t_null; + } + return t; +} + +void Curl_thread_destroy(curl_thread_t hnd) +{ + CloseHandle(hnd); +} + +int Curl_thread_join(curl_thread_t *hnd) +{ +#if !defined(_WIN32_WINNT) || !defined(_WIN32_WINNT_VISTA) || \ + (_WIN32_WINNT < _WIN32_WINNT_VISTA) + int ret = (WaitForSingleObject(*hnd, INFINITE) == WAIT_OBJECT_0); +#else + int ret = (WaitForSingleObjectEx(*hnd, INFINITE, FALSE) == WAIT_OBJECT_0); +#endif + + Curl_thread_destroy(*hnd); + + *hnd = curl_thread_t_null; + + return ret; +} + +#endif /* USE_THREADS_* */ diff --git a/Utilities/cmcurl/lib/curl_threads.h b/Utilities/cmcurl/lib/curl_threads.h new file mode 100644 index 0000000..27a478d --- /dev/null +++ b/Utilities/cmcurl/lib/curl_threads.h @@ -0,0 +1,65 @@ +#ifndef HEADER_CURL_THREADS_H +#define HEADER_CURL_THREADS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if defined(USE_THREADS_POSIX) +# 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) +#elif defined(USE_THREADS_WIN32) +# define CURL_STDCALL __stdcall +# define curl_mutex_t CRITICAL_SECTION +# define curl_thread_t HANDLE +# define curl_thread_t_null (HANDLE)0 +# if !defined(_WIN32_WINNT) || !defined(_WIN32_WINNT_VISTA) || \ + (_WIN32_WINNT < _WIN32_WINNT_VISTA) +# define Curl_mutex_init(m) InitializeCriticalSection(m) +# else +# define Curl_mutex_init(m) InitializeCriticalSectionEx(m, 0, 1) +# endif +# define Curl_mutex_acquire(m) EnterCriticalSection(m) +# define Curl_mutex_release(m) LeaveCriticalSection(m) +# define Curl_mutex_destroy(m) DeleteCriticalSection(m) +#endif + +#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) + +/* !checksrc! disable SPACEBEFOREPAREN 1 */ +curl_thread_t Curl_thread_create(unsigned int (CURL_STDCALL *func) (void *), + void *arg); + +void Curl_thread_destroy(curl_thread_t hnd); + +int Curl_thread_join(curl_thread_t *hnd); + +#endif /* USE_THREADS_POSIX || USE_THREADS_WIN32 */ + +#endif /* HEADER_CURL_THREADS_H */ diff --git a/Utilities/cmcurl/lib/curl_trc.c b/Utilities/cmcurl/lib/curl_trc.c new file mode 100644 index 0000000..0ebe40b --- /dev/null +++ b/Utilities/cmcurl/lib/curl_trc.c @@ -0,0 +1,241 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "curl_trc.h" +#include "urldata.h" +#include "easyif.h" +#include "cfilters.h" +#include "timeval.h" +#include "multiif.h" +#include "strcase.h" + +#include "cf-socket.h" +#include "connect.h" +#include "http2.h" +#include "http_proxy.h" +#include "cf-h1-proxy.h" +#include "cf-h2-proxy.h" +#include "cf-haproxy.h" +#include "cf-https-connect.h" +#include "socks.h" +#include "strtok.h" +#include "vtls/vtls.h" +#include "vquic/vquic.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +void Curl_debug(struct Curl_easy *data, curl_infotype type, + char *ptr, size_t size) +{ + if(data->set.verbose) { + static const char s_infotype[CURLINFO_END][3] = { + "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; + if(data->set.fdebug) { + bool inCallback = Curl_is_in_callback(data); + Curl_set_in_callback(data, true); + (void)(*data->set.fdebug)(data, type, ptr, size, data->set.debugdata); + Curl_set_in_callback(data, inCallback); + } + else { + switch(type) { + case CURLINFO_TEXT: + case CURLINFO_HEADER_OUT: + case CURLINFO_HEADER_IN: + fwrite(s_infotype[type], 2, 1, data->set.err); + fwrite(ptr, size, 1, data->set.err); + break; + default: /* nada */ + break; + } + } + } +} + + +/* Curl_failf() is for messages stating why we failed. + * The message SHALL NOT include any LF or CR. + */ +void Curl_failf(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(data->set.verbose || data->set.errorbuffer) { + va_list ap; + int len; + char error[CURL_ERROR_SIZE + 2]; + va_start(ap, fmt); + len = mvsnprintf(error, CURL_ERROR_SIZE, fmt, ap); + + if(data->set.errorbuffer && !data->state.errorbuf) { + strcpy(data->set.errorbuffer, error); + data->state.errorbuf = TRUE; /* wrote error string */ + } + error[len++] = '\n'; + error[len] = '\0'; + Curl_debug(data, CURLINFO_TEXT, error, len); + va_end(ap); + } +} + +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + +/* Curl_infof() is for info message along the way */ +#define MAXINFO 2048 + +void Curl_infof(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(data && data->set.verbose) { + va_list ap; + int len; + char buffer[MAXINFO + 2]; + va_start(ap, fmt); + len = mvsnprintf(buffer, MAXINFO, fmt, ap); + va_end(ap); + buffer[len++] = '\n'; + buffer[len] = '\0'; + Curl_debug(data, CURLINFO_TEXT, buffer, len); + } +} + +void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf, + const char *fmt, ...) +{ + DEBUGASSERT(cf); + if(Curl_trc_cf_is_verbose(cf, data)) { + va_list ap; + int len; + char buffer[MAXINFO + 2]; + len = msnprintf(buffer, MAXINFO, "[%s] ", cf->cft->name); + va_start(ap, fmt); + len += mvsnprintf(buffer + len, MAXINFO - len, fmt, ap); + va_end(ap); + buffer[len++] = '\n'; + buffer[len] = '\0'; + Curl_debug(data, CURLINFO_TEXT, buffer, len); + } +} + + +static struct Curl_cftype *cf_types[] = { + &Curl_cft_tcp, + &Curl_cft_udp, + &Curl_cft_unix, + &Curl_cft_tcp_accept, + &Curl_cft_happy_eyeballs, + &Curl_cft_setup, +#ifdef USE_NGHTTP2 + &Curl_cft_nghttp2, +#endif +#ifdef USE_SSL + &Curl_cft_ssl, + &Curl_cft_ssl_proxy, +#endif +#if !defined(CURL_DISABLE_PROXY) +#if !defined(CURL_DISABLE_HTTP) + &Curl_cft_h1_proxy, +#ifdef USE_NGHTTP2 + &Curl_cft_h2_proxy, +#endif + &Curl_cft_http_proxy, +#endif /* !CURL_DISABLE_HTTP */ + &Curl_cft_haproxy, + &Curl_cft_socks_proxy, +#endif /* !CURL_DISABLE_PROXY */ +#ifdef ENABLE_QUIC + &Curl_cft_http3, +#endif +#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) + &Curl_cft_http_connect, +#endif + NULL, +}; + +CURLcode Curl_trc_opt(const char *config) +{ + char *token, *tok_buf, *tmp; + size_t i; + int lvl; + + tmp = strdup(config); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + + token = strtok_r(tmp, ", ", &tok_buf); + while(token) { + switch(*token) { + case '-': + lvl = CURL_LOG_LVL_NONE; + ++token; + break; + case '+': + lvl = CURL_LOG_LVL_INFO; + ++token; + break; + default: + lvl = CURL_LOG_LVL_INFO; + break; + } + for(i = 0; cf_types[i]; ++i) { + if(strcasecompare(token, "all")) { + cf_types[i]->log_level = lvl; + } + else if(strcasecompare(token, cf_types[i]->name)) { + cf_types[i]->log_level = lvl; + break; + } + } + token = strtok_r(NULL, ", ", &tok_buf); + } + free(tmp); + return CURLE_OK; +} + +CURLcode Curl_trc_init(void) +{ +#ifdef DEBUGBUILD + /* WIP: we use the auto-init from an env var only in DEBUG builds for + * convenience. */ + const char *config = getenv("CURL_DEBUG"); + if(config) { + return Curl_trc_opt(config); + } +#endif /* DEBUGBUILD */ + return CURLE_OK; +} +#else /* defined(CURL_DISABLE_VERBOSE_STRINGS) */ + +CURLcode Curl_trc_init(void) +{ + return CURLE_OK; +} + +#endif /* !defined(CURL_DISABLE_VERBOSE_STRINGS) */ diff --git a/Utilities/cmcurl/lib/curl_trc.h b/Utilities/cmcurl/lib/curl_trc.h new file mode 100644 index 0000000..ade9108 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_trc.h @@ -0,0 +1,146 @@ +#ifndef HEADER_CURL_TRC_H +#define HEADER_CURL_TRC_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +struct Curl_easy; +struct Curl_cfilter; + +/** + * Init logging, return != 0 on failure. + */ +CURLcode Curl_trc_init(void); + +/** + * Configure tracing. May be called several times during global + * initialization. Later calls may not take effect. + * + * Configuration format supported: + * - comma-separated list of component names to enable logging on. + * E.g. 'http/2,ssl'. Unknown names are ignored. Names are compared + * case-insensitive. + * - component 'all' applies to all known log components + * - prefixing a component with '+' or '-' will en-/disable logging for + * that component + * Example: 'all,-ssl' would enable logging for all components but the + * SSL filters. + * + * @param config configuration string + */ +CURLcode Curl_trc_opt(const char *config); + +/* the function used to output verbose information */ +void Curl_debug(struct Curl_easy *data, curl_infotype type, + char *ptr, size_t size); + +/** + * Output a failure message on registered callbacks for transfer. + */ +void Curl_failf(struct Curl_easy *data, +#if defined(__GNUC__) && !defined(printf) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__MINGW32__) + const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +#else + const char *fmt, ...); +#endif + +#define failf Curl_failf + +#define CURL_LOG_LVL_NONE 0 +#define CURL_LOG_LVL_INFO 1 + + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define CURL_HAVE_C99 +#endif + +#ifdef CURL_HAVE_C99 +#define infof(data, ...) \ + do { if(Curl_trc_is_verbose(data)) \ + Curl_infof(data, __VA_ARGS__); } while(0) +#define CURL_TRC_CF(data, cf, ...) \ + do { if(Curl_trc_cf_is_verbose(cf, data)) \ + Curl_trc_cf_infof(data, cf, __VA_ARGS__); } while(0) + +#else +#define infof Curl_infof +#define CURL_TRC_CF Curl_trc_cf_infof +#endif + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +/* informational messages enabled */ + +#define Curl_trc_is_verbose(data) ((data) && (data)->set.verbose) +#define Curl_trc_cf_is_verbose(cf, data) \ + ((data) && (data)->set.verbose && \ + (cf) && (cf)->cft->log_level >= CURL_LOG_LVL_INFO) + +/** + * Output an informational message when transfer's verbose logging is enabled. + */ +void Curl_infof(struct Curl_easy *data, +#if defined(__GNUC__) && !defined(printf) && defined(CURL_HAVE_C99) && \ + !defined(__MINGW32__) + const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +#else + const char *fmt, ...); +#endif + +/** + * Output an informational message when both transfer's verbose logging + * and connection filters verbose logging are enabled. + */ +void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf, +#if defined(__GNUC__) && !defined(printf) && defined(CURL_HAVE_C99) && \ + !defined(__MINGW32__) + const char *fmt, ...) + __attribute__((format(printf, 3, 4))); +#else + const char *fmt, ...); +#endif + +#else /* defined(CURL_DISABLE_VERBOSE_STRINGS) */ +/* All informational messages are not compiled in for size savings */ + +#define Curl_trc_is_verbose(d) ((void)(d), FALSE) +#define Curl_trc_cf_is_verbose(x,y) ((void)(x), (void)(y), FALSE) + +static void Curl_infof(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +static void Curl_trc_cf_infof(struct Curl_easy *data, + struct Curl_cfilter *cf, + const char *fmt, ...) +{ + (void)data; (void)cf; (void)fmt; +} + +#endif /* !defined(CURL_DISABLE_VERBOSE_STRINGS) */ + +#endif /* HEADER_CURL_TRC_H */ diff --git a/Utilities/cmcurl/lib/curlx.h b/Utilities/cmcurl/lib/curlx.h new file mode 100644 index 0000000..7a753d6 --- /dev/null +++ b/Utilities/cmcurl/lib/curlx.h @@ -0,0 +1,118 @@ +#ifndef HEADER_CURL_CURLX_H +#define HEADER_CURL_CURLX_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Defines protos and includes all header files that provide the curlx_* + * functions. The curlx_* functions are not part of the libcurl API, but are + * stand-alone functions whose sources can be built and linked by apps if need + * be. + */ + +#include <curl/mprintf.h> +/* this is still a public header file that provides the curl_mprintf() + functions while they still are offered publicly. They will be made library- + private one day */ + +#include "strcase.h" +/* "strcase.h" provides the strcasecompare protos */ + +#include "strtoofft.h" +/* "strtoofft.h" provides this function: curlx_strtoofft(), returns a + curl_off_t number from a given string. +*/ + +#include "nonblock.h" +/* "nonblock.h" provides curlx_nonblock() */ + +#include "warnless.h" +/* "warnless.h" provides functions: + + curlx_ultous() + curlx_ultouc() + curlx_uztosi() +*/ + +#include "curl_multibyte.h" +/* "curl_multibyte.h" provides these functions and macros: + + curlx_convert_UTF8_to_wchar() + curlx_convert_wchar_to_UTF8() + curlx_convert_UTF8_to_tchar() + curlx_convert_tchar_to_UTF8() + curlx_unicodefree() +*/ + +#include "version_win32.h" +/* "version_win32.h" provides curlx_verify_windows_version() */ + +/* Now setup curlx_ * names for the functions that are to become curlx_ and + be removed from a future libcurl official API: + curlx_getenv + curlx_mprintf (and its variations) + curlx_strcasecompare + curlx_strncasecompare + +*/ + +#define curlx_getenv curl_getenv +#define curlx_mvsnprintf curl_mvsnprintf +#define curlx_msnprintf curl_msnprintf +#define curlx_maprintf curl_maprintf +#define curlx_mvaprintf curl_mvaprintf +#define curlx_msprintf curl_msprintf +#define curlx_mprintf curl_mprintf +#define curlx_mfprintf curl_mfprintf +#define curlx_mvsprintf curl_mvsprintf +#define curlx_mvprintf curl_mvprintf +#define curlx_mvfprintf curl_mvfprintf + +#ifdef ENABLE_CURLX_PRINTF +/* If this define is set, we define all "standard" printf() functions to use + the curlx_* version instead. It makes the source code transparent and + easier to understand/patch. Undefine them first. */ +# undef printf +# undef fprintf +# undef sprintf +# undef msnprintf +# undef vprintf +# undef vfprintf +# undef vsprintf +# undef mvsnprintf +# undef aprintf +# undef vaprintf + +# define printf curlx_mprintf +# define fprintf curlx_mfprintf +# define sprintf curlx_msprintf +# define msnprintf curlx_msnprintf +# define vprintf curlx_mvprintf +# define vfprintf curlx_mvfprintf +# define mvsnprintf curlx_mvsnprintf +# define aprintf curlx_maprintf +# define vaprintf curlx_mvaprintf +#endif /* ENABLE_CURLX_PRINTF */ + +#endif /* HEADER_CURL_CURLX_H */ diff --git a/Utilities/cmcurl/lib/dict.c b/Utilities/cmcurl/lib/dict.c new file mode 100644 index 0000000..3172b38 --- /dev/null +++ b/Utilities/cmcurl/lib/dict.c @@ -0,0 +1,320 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_DICT + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#elif defined(HAVE_UNISTD_H) +#include <unistd.h> +#endif + +#include "urldata.h" +#include <curl/curl.h> +#include "transfer.h" +#include "sendf.h" +#include "escape.h" +#include "progress.h" +#include "dict.h" +#include "curl_printf.h" +#include "strcase.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/* + * Forward declarations. + */ + +static CURLcode dict_do(struct Curl_easy *data, bool *done); + +/* + * DICT protocol handler. + */ + +const struct Curl_handler Curl_handler_dict = { + "DICT", /* scheme */ + ZERO_NULL, /* setup_connection */ + dict_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_DICT, /* defport */ + CURLPROTO_DICT, /* protocol */ + CURLPROTO_DICT, /* family */ + PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ +}; + +#define DYN_DICT_WORD 10000 +static char *unescape_word(const char *input) +{ + struct dynbuf out; + const char *ptr; + CURLcode result = CURLE_OK; + Curl_dyn_init(&out, DYN_DICT_WORD); + + /* According to RFC2229 section 2.2, these letters need to be escaped with + \[letter] */ + for(ptr = input; *ptr; ptr++) { + char ch = *ptr; + if((ch <= 32) || (ch == 127) || + (ch == '\'') || (ch == '\"') || (ch == '\\')) + result = Curl_dyn_addn(&out, "\\", 1); + if(!result) + result = Curl_dyn_addn(&out, ptr, 1); + if(result) + return NULL; + } + return Curl_dyn_ptr(&out); +} + +/* sendf() sends formatted data to the server */ +static CURLcode sendf(curl_socket_t sockfd, struct Curl_easy *data, + const char *fmt, ...) +{ + ssize_t bytes_written; + size_t write_len; + CURLcode result = CURLE_OK; + char *s; + char *sptr; + va_list ap; + va_start(ap, fmt); + s = vaprintf(fmt, ap); /* returns an allocated string */ + va_end(ap); + if(!s) + return CURLE_OUT_OF_MEMORY; /* failure */ + + bytes_written = 0; + write_len = strlen(s); + sptr = s; + + for(;;) { + /* Write the buffer to the socket */ + result = Curl_write(data, sockfd, sptr, write_len, &bytes_written); + + if(result) + break; + + Curl_debug(data, CURLINFO_DATA_OUT, sptr, (size_t)bytes_written); + + if((size_t)bytes_written != write_len) { + /* if not all was written at once, we must advance the pointer, decrease + the size left and try again! */ + write_len -= bytes_written; + sptr += bytes_written; + } + else + break; + } + + free(s); /* free the output string */ + + return result; +} + +static CURLcode dict_do(struct Curl_easy *data, bool *done) +{ + char *word; + char *eword = NULL; + char *ppath; + char *database = NULL; + char *strategy = NULL; + char *nthdef = NULL; /* This is not part of the protocol, but required + by RFC 2229 */ + CURLcode result; + struct connectdata *conn = data->conn; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + + char *path; + + *done = TRUE; /* unconditionally */ + + /* url-decode path before further evaluation */ + result = Curl_urldecode(data->state.up.path, 0, &path, NULL, REJECT_CTRL); + if(result) + return result; + + if(strncasecompare(path, DICT_MATCH, sizeof(DICT_MATCH)-1) || + strncasecompare(path, DICT_MATCH2, sizeof(DICT_MATCH2)-1) || + strncasecompare(path, DICT_MATCH3, sizeof(DICT_MATCH3)-1)) { + + word = strchr(path, ':'); + if(word) { + word++; + database = strchr(word, ':'); + if(database) { + *database++ = (char)0; + strategy = strchr(database, ':'); + if(strategy) { + *strategy++ = (char)0; + nthdef = strchr(strategy, ':'); + if(nthdef) { + *nthdef = (char)0; + } + } + } + } + + if(!word || (*word == (char)0)) { + infof(data, "lookup word is missing"); + word = (char *)"default"; + } + if(!database || (*database == (char)0)) { + database = (char *)"!"; + } + if(!strategy || (*strategy == (char)0)) { + strategy = (char *)"."; + } + + eword = unescape_word(word); + if(!eword) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + result = sendf(sockfd, data, + "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" + "MATCH " + "%s " /* database */ + "%s " /* strategy */ + "%s\r\n" /* word */ + "QUIT\r\n", + database, + strategy, + eword); + + if(result) { + failf(data, "Failed sending DICT request"); + goto error; + } + Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); /* no upload */ + } + else if(strncasecompare(path, DICT_DEFINE, sizeof(DICT_DEFINE)-1) || + strncasecompare(path, DICT_DEFINE2, sizeof(DICT_DEFINE2)-1) || + strncasecompare(path, DICT_DEFINE3, sizeof(DICT_DEFINE3)-1)) { + + word = strchr(path, ':'); + if(word) { + word++; + database = strchr(word, ':'); + if(database) { + *database++ = (char)0; + nthdef = strchr(database, ':'); + if(nthdef) { + *nthdef = (char)0; + } + } + } + + if(!word || (*word == (char)0)) { + infof(data, "lookup word is missing"); + word = (char *)"default"; + } + if(!database || (*database == (char)0)) { + database = (char *)"!"; + } + + eword = unescape_word(word); + if(!eword) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + result = sendf(sockfd, data, + "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" + "DEFINE " + "%s " /* database */ + "%s\r\n" /* word */ + "QUIT\r\n", + database, + eword); + + if(result) { + failf(data, "Failed sending DICT request"); + goto error; + } + Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + } + else { + + ppath = strchr(path, '/'); + if(ppath) { + int i; + + ppath++; + for(i = 0; ppath[i]; i++) { + if(ppath[i] == ':') + ppath[i] = ' '; + } + result = sendf(sockfd, data, + "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" + "%s\r\n" + "QUIT\r\n", ppath); + if(result) { + failf(data, "Failed sending DICT request"); + goto error; + } + + Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + } + } + +error: + free(eword); + free(path); + return result; +} +#endif /* CURL_DISABLE_DICT */ diff --git a/Utilities/cmcurl/lib/dict.h b/Utilities/cmcurl/lib/dict.h new file mode 100644 index 0000000..ba9a927 --- /dev/null +++ b/Utilities/cmcurl/lib/dict.h @@ -0,0 +1,31 @@ +#ifndef HEADER_CURL_DICT_H +#define HEADER_CURL_DICT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#ifndef CURL_DISABLE_DICT +extern const struct Curl_handler Curl_handler_dict; +#endif + +#endif /* HEADER_CURL_DICT_H */ diff --git a/Utilities/cmcurl/lib/doh.c b/Utilities/cmcurl/lib/doh.c new file mode 100644 index 0000000..1d928e9 --- /dev/null +++ b/Utilities/cmcurl/lib/doh.c @@ -0,0 +1,998 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_DOH + +#include "urldata.h" +#include "curl_addrinfo.h" +#include "doh.h" + +#include "sendf.h" +#include "multiif.h" +#include "url.h" +#include "share.h" +#include "curl_base64.h" +#include "connect.h" +#include "strdup.h" +#include "dynbuf.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define DNS_CLASS_IN 0x01 + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static const char * const errors[]={ + "", + "Bad label", + "Out of range", + "Label loop", + "Too small", + "Out of memory", + "RDATA length", + "Malformat", + "Bad RCODE", + "Unexpected TYPE", + "Unexpected CLASS", + "No content", + "Bad ID", + "Name too long" +}; + +static const char *doh_strerror(DOHcode code) +{ + if((code >= DOH_OK) && (code <= DOH_DNS_NAME_TOO_LONG)) + return errors[code]; + return "bad error code"; +} +#endif + +/* @unittest 1655 + */ +UNITTEST DOHcode doh_encode(const char *host, + DNStype dnstype, + unsigned char *dnsp, /* buffer */ + size_t len, /* buffer size */ + size_t *olen) /* output length */ +{ + const size_t hostlen = strlen(host); + unsigned char *orig = dnsp; + const char *hostp = host; + + /* The expected output length is 16 bytes more than the length of + * the QNAME-encoding of the host name. + * + * A valid DNS name may not contain a zero-length label, except at + * the end. For this reason, a name beginning with a dot, or + * containing a sequence of two or more consecutive dots, is invalid + * and cannot be encoded as a QNAME. + * + * If the host name ends with a trailing dot, the corresponding + * QNAME-encoding is one byte longer than the host name. If (as is + * also valid) the hostname is shortened by the omission of the + * trailing dot, then its QNAME-encoding will be two bytes longer + * than the host name. + * + * Each [ label, dot ] pair is encoded as [ length, label ], + * preserving overall length. A final [ label ] without a dot is + * also encoded as [ length, label ], increasing overall length + * by one. The encoding is completed by appending a zero byte, + * representing the zero-length root label, again increasing + * the overall length by one. + */ + + size_t expected_len; + DEBUGASSERT(hostlen); + expected_len = 12 + 1 + hostlen + 4; + if(host[hostlen-1]!='.') + expected_len++; + + if(expected_len > (256 + 16)) /* RFCs 1034, 1035 */ + return DOH_DNS_NAME_TOO_LONG; + + if(len < expected_len) + return DOH_TOO_SMALL_BUFFER; + + *dnsp++ = 0; /* 16 bit id */ + *dnsp++ = 0; + *dnsp++ = 0x01; /* |QR| Opcode |AA|TC|RD| Set the RD bit */ + *dnsp++ = '\0'; /* |RA| Z | RCODE | */ + *dnsp++ = '\0'; + *dnsp++ = 1; /* QDCOUNT (number of entries in the question section) */ + *dnsp++ = '\0'; + *dnsp++ = '\0'; /* ANCOUNT */ + *dnsp++ = '\0'; + *dnsp++ = '\0'; /* NSCOUNT */ + *dnsp++ = '\0'; + *dnsp++ = '\0'; /* ARCOUNT */ + + /* encode each label and store it in the QNAME */ + while(*hostp) { + size_t labellen; + char *dot = strchr(hostp, '.'); + if(dot) + labellen = dot - hostp; + else + labellen = strlen(hostp); + if((labellen > 63) || (!labellen)) { + /* label is too long or too short, error out */ + *olen = 0; + return DOH_DNS_BAD_LABEL; + } + /* label is non-empty, process it */ + *dnsp++ = (unsigned char)labellen; + memcpy(dnsp, hostp, labellen); + dnsp += labellen; + hostp += labellen; + /* advance past dot, but only if there is one */ + if(dot) + hostp++; + } /* next label */ + + *dnsp++ = 0; /* append zero-length label for root */ + + /* There are assigned TYPE codes beyond 255: use range [1..65535] */ + *dnsp++ = (unsigned char)(255 & (dnstype>>8)); /* upper 8 bit TYPE */ + *dnsp++ = (unsigned char)(255 & dnstype); /* lower 8 bit TYPE */ + + *dnsp++ = '\0'; /* upper 8 bit CLASS */ + *dnsp++ = DNS_CLASS_IN; /* IN - "the Internet" */ + + *olen = dnsp - orig; + + /* verify that our estimation of length is valid, since + * this has led to buffer overflows in this function */ + DEBUGASSERT(*olen == expected_len); + return DOH_OK; +} + +static size_t +doh_write_cb(const void *contents, size_t size, size_t nmemb, void *userp) +{ + size_t realsize = size * nmemb; + struct dynbuf *mem = (struct dynbuf *)userp; + + if(Curl_dyn_addn(mem, contents, realsize)) + return 0; + + return realsize; +} + +/* called from multi.c when this DoH transfer is complete */ +static int doh_done(struct Curl_easy *doh, CURLcode result) +{ + struct Curl_easy *data = doh->set.dohfor; + struct dohdata *dohp = data->req.doh; + /* so one of the DoH request done for the 'data' transfer is now complete! */ + dohp->pending--; + infof(data, "a DoH request is completed, %u to go", dohp->pending); + if(result) + infof(data, "DoH request %s", curl_easy_strerror(result)); + + if(!dohp->pending) { + /* DoH completed */ + curl_slist_free_all(dohp->headers); + dohp->headers = NULL; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + return 0; +} + +#define ERROR_CHECK_SETOPT(x,y) \ +do { \ + result = curl_easy_setopt(doh, x, y); \ + if(result && \ + result != CURLE_NOT_BUILT_IN && \ + result != CURLE_UNKNOWN_OPTION) \ + goto error; \ +} while(0) + +static CURLcode dohprobe(struct Curl_easy *data, + struct dnsprobe *p, DNStype dnstype, + const char *host, + const char *url, CURLM *multi, + struct curl_slist *headers) +{ + struct Curl_easy *doh = NULL; + char *nurl = NULL; + CURLcode result = CURLE_OK; + timediff_t timeout_ms; + DOHcode d = doh_encode(host, dnstype, p->dohbuffer, sizeof(p->dohbuffer), + &p->dohlen); + if(d) { + failf(data, "Failed to encode DoH packet [%d]", d); + return CURLE_OUT_OF_MEMORY; + } + + p->dnstype = dnstype; + Curl_dyn_init(&p->serverdoh, DYN_DOH_RESPONSE); + + timeout_ms = Curl_timeleft(data, NULL, TRUE); + if(timeout_ms <= 0) { + result = CURLE_OPERATION_TIMEDOUT; + goto error; + } + /* Curl_open() is the internal version of curl_easy_init() */ + result = Curl_open(&doh); + if(!result) { + /* pass in the struct pointer via a local variable to please coverity and + the gcc typecheck helpers */ + struct dynbuf *resp = &p->serverdoh; + doh->state.internal = true; + ERROR_CHECK_SETOPT(CURLOPT_URL, url); + ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https"); + ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb); + ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, resp); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, p->dohbuffer); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)p->dohlen); + ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers); +#ifdef USE_HTTP2 + ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); + ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L); +#endif +#ifndef CURLDEBUG + /* enforce HTTPS if not debug */ + ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); +#else + /* in debug mode, also allow http */ + ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS); +#endif + ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms); + ERROR_CHECK_SETOPT(CURLOPT_SHARE, data->share); + if(data->set.err && data->set.err != stderr) + ERROR_CHECK_SETOPT(CURLOPT_STDERR, data->set.err); + if(data->set.verbose) + 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. + */ + if(data->set.ssl.falsestart) + ERROR_CHECK_SETOPT(CURLOPT_SSL_FALSESTART, 1L); + 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]); + } + + { + long mask = + (data->set.ssl.enable_beast ? + CURLSSLOPT_ALLOW_BEAST : 0) | + (data->set.ssl.no_revoke ? + CURLSSLOPT_NO_REVOKE : 0) | + (data->set.ssl.no_partialchain ? + CURLSSLOPT_NO_PARTIALCHAIN : 0) | + (data->set.ssl.revoke_best_effort ? + CURLSSLOPT_REVOKE_BEST_EFFORT : 0) | + (data->set.ssl.native_ca_store ? + CURLSSLOPT_NATIVE_CA : 0) | + (data->set.ssl.auto_client_cert ? + CURLSSLOPT_AUTO_CLIENT_CERT : 0); + + (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, mask); + } + + doh->set.fmultidone = doh_done; + doh->set.dohfor = data; /* identify for which transfer this is done */ + p->easy = doh; + + /* DoH handles must not inherit private_data. The handles may be passed to + the user via callbacks and the user will be able to identify them as + internal handles because private data is not set. The user can then set + private_data via CURLOPT_PRIVATE if they so choose. */ + DEBUGASSERT(!doh->set.private_data); + + if(curl_multi_add_handle(multi, doh)) + goto error; + } + else + goto error; + free(nurl); + return CURLE_OK; + +error: + free(nurl); + Curl_close(&doh); + return result; +} + +/* + * Curl_doh() resolves a name using DoH. It resolves a name and returns a + * 'Curl_addrinfo *' with the address information. + */ + +struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) +{ + CURLcode result = CURLE_OK; + int slot; + struct dohdata *dohp; + struct connectdata *conn = data->conn; + *waitp = FALSE; + (void)hostname; + (void)port; + + DEBUGASSERT(!data->req.doh); + DEBUGASSERT(conn); + + /* start clean, consider allocating this struct on demand */ + dohp = data->req.doh = calloc(1, sizeof(struct dohdata)); + if(!dohp) + return NULL; + + conn->bits.doh = TRUE; + dohp->host = hostname; + dohp->port = port; + dohp->headers = + curl_slist_append(NULL, + "Content-Type: application/dns-message"); + if(!dohp->headers) + goto error; + + /* create IPv4 DoH request */ + result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V4], + DNS_TYPE_A, hostname, data->set.str[STRING_DOH], + data->multi, dohp->headers); + if(result) + goto error; + dohp->pending++; + +#ifdef ENABLE_IPV6 + if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + /* create IPv6 DoH request */ + result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V6], + DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH], + data->multi, dohp->headers); + if(result) + goto error; + dohp->pending++; + } +#endif + *waitp = TRUE; /* this never returns synchronously */ + return NULL; + +error: + curl_slist_free_all(dohp->headers); + data->req.doh->headers = NULL; + for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) { + (void)curl_multi_remove_handle(data->multi, dohp->probe[slot].easy); + Curl_close(&dohp->probe[slot].easy); + } + Curl_safefree(data->req.doh); + return NULL; +} + +static DOHcode skipqname(const unsigned char *doh, size_t dohlen, + unsigned int *indexp) +{ + unsigned char length; + do { + if(dohlen < (*indexp + 1)) + return DOH_DNS_OUT_OF_RANGE; + length = doh[*indexp]; + if((length & 0xc0) == 0xc0) { + /* name pointer, advance over it and be done */ + if(dohlen < (*indexp + 2)) + return DOH_DNS_OUT_OF_RANGE; + *indexp += 2; + break; + } + if(length & 0xc0) + return DOH_DNS_BAD_LABEL; + if(dohlen < (*indexp + 1 + length)) + return DOH_DNS_OUT_OF_RANGE; + *indexp += 1 + length; + } while(length); + return DOH_OK; +} + +static unsigned short get16bit(const unsigned char *doh, int index) +{ + return (unsigned short)((doh[index] << 8) | doh[index + 1]); +} + +static unsigned int get32bit(const unsigned char *doh, int index) +{ + /* make clang and gcc optimize this to bswap by incrementing + the pointer first. */ + doh += index; + + /* avoid undefined behavior by casting to unsigned before shifting + 24 bits, possibly into the sign bit. codegen is same, but + ub sanitizer won't be upset */ + return ( (unsigned)doh[0] << 24) | (doh[1] << 16) |(doh[2] << 8) | doh[3]; +} + +static DOHcode store_a(const unsigned char *doh, int index, struct dohentry *d) +{ + /* silently ignore addresses over the limit */ + if(d->numaddr < DOH_MAX_ADDR) { + struct dohaddr *a = &d->addr[d->numaddr]; + a->type = DNS_TYPE_A; + memcpy(&a->ip.v4, &doh[index], 4); + d->numaddr++; + } + return DOH_OK; +} + +static DOHcode store_aaaa(const unsigned char *doh, + int index, + struct dohentry *d) +{ + /* silently ignore addresses over the limit */ + if(d->numaddr < DOH_MAX_ADDR) { + struct dohaddr *a = &d->addr[d->numaddr]; + a->type = DNS_TYPE_AAAA; + memcpy(&a->ip.v6, &doh[index], 16); + d->numaddr++; + } + return DOH_OK; +} + +static DOHcode 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 = newpos; + continue; + } + else if(length & 0xc0) + return DOH_DNS_BAD_LABEL; /* bad input */ + else + index++; + + if(length) { + if(Curl_dyn_len(c)) { + if(Curl_dyn_addn(c, STRCONST("."))) + return DOH_OUT_OF_MEM; + } + if((index + length) > dohlen) + return DOH_DNS_BAD_LABEL; + + if(Curl_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 rdata(const unsigned char *doh, + size_t dohlen, + unsigned short rdlength, + unsigned short type, + int index, + struct dohentry *d) +{ + /* RDATA + - A (TYPE 1): 4 bytes + - AAAA (TYPE 28): 16 bytes + - NS (TYPE 2): N bytes */ + DOHcode rc; + + switch(type) { + case DNS_TYPE_A: + if(rdlength != 4) + return DOH_DNS_RDATA_LEN; + rc = store_a(doh, index, d); + if(rc) + return rc; + break; + case DNS_TYPE_AAAA: + if(rdlength != 16) + return DOH_DNS_RDATA_LEN; + rc = store_aaaa(doh, index, d); + if(rc) + return rc; + break; + case DNS_TYPE_CNAME: + rc = store_cname(doh, dohlen, index, d); + if(rc) + return rc; + break; + case DNS_TYPE_DNAME: + /* explicit for clarity; just skip; rely on synthesized CNAME */ + break; + default: + /* unsupported type, just skip it */ + break; + } + return DOH_OK; +} + +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++) + Curl_dyn_init(&de->cname[i], DYN_DOH_CNAME); +} + + +UNITTEST DOHcode doh_decode(const unsigned char *doh, + size_t dohlen, + DNStype dnstype, + struct dohentry *d) +{ + unsigned char rcode; + unsigned short qdcount; + unsigned short ancount; + unsigned short type = 0; + unsigned short rdlength; + unsigned short nscount; + unsigned short arcount; + unsigned int index = 12; + DOHcode rc; + + if(dohlen < 12) + return DOH_TOO_SMALL_BUFFER; /* too small */ + if(!doh || doh[0] || doh[1]) + return DOH_DNS_BAD_ID; /* bad ID */ + rcode = doh[3] & 0x0f; + if(rcode) + return DOH_DNS_BAD_RCODE; /* bad rcode */ + + qdcount = get16bit(doh, 4); + while(qdcount) { + rc = skipqname(doh, dohlen, &index); + if(rc) + return rc; /* bad qname */ + if(dohlen < (index + 4)) + return DOH_DNS_OUT_OF_RANGE; + index += 4; /* skip question's type and class */ + qdcount--; + } + + ancount = get16bit(doh, 6); + while(ancount) { + unsigned short class; + unsigned int ttl; + + rc = skipqname(doh, dohlen, &index); + if(rc) + return rc; /* bad qname */ + + if(dohlen < (index + 2)) + return DOH_DNS_OUT_OF_RANGE; + + type = get16bit(doh, index); + if((type != DNS_TYPE_CNAME) /* may be synthesized from DNAME */ + && (type != DNS_TYPE_DNAME) /* if present, accept and ignore */ + && (type != dnstype)) + /* Not the same type as was asked for nor CNAME nor DNAME */ + return DOH_DNS_UNEXPECTED_TYPE; + index += 2; + + if(dohlen < (index + 2)) + return DOH_DNS_OUT_OF_RANGE; + class = get16bit(doh, index); + if(DNS_CLASS_IN != class) + return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */ + index += 2; + + if(dohlen < (index + 4)) + return DOH_DNS_OUT_OF_RANGE; + + ttl = get32bit(doh, index); + if(ttl < d->ttl) + d->ttl = ttl; + index += 4; + + if(dohlen < (index + 2)) + return DOH_DNS_OUT_OF_RANGE; + + rdlength = get16bit(doh, index); + index += 2; + if(dohlen < (index + rdlength)) + return DOH_DNS_OUT_OF_RANGE; + + rc = rdata(doh, dohlen, rdlength, type, index, d); + if(rc) + return rc; /* bad rdata */ + index += rdlength; + ancount--; + } + + nscount = get16bit(doh, 8); + while(nscount) { + rc = skipqname(doh, dohlen, &index); + if(rc) + return rc; /* bad qname */ + + if(dohlen < (index + 8)) + return DOH_DNS_OUT_OF_RANGE; + + index += 2 + 2 + 4; /* type, class and ttl */ + + if(dohlen < (index + 2)) + return DOH_DNS_OUT_OF_RANGE; + + rdlength = get16bit(doh, index); + index += 2; + if(dohlen < (index + rdlength)) + return DOH_DNS_OUT_OF_RANGE; + index += rdlength; + nscount--; + } + + arcount = get16bit(doh, 10); + while(arcount) { + rc = skipqname(doh, dohlen, &index); + if(rc) + return rc; /* bad qname */ + + if(dohlen < (index + 8)) + return DOH_DNS_OUT_OF_RANGE; + + index += 2 + 2 + 4; /* type, class and ttl */ + + if(dohlen < (index + 2)) + return DOH_DNS_OUT_OF_RANGE; + + rdlength = get16bit(doh, index); + index += 2; + if(dohlen < (index + rdlength)) + return DOH_DNS_OUT_OF_RANGE; + index += rdlength; + arcount--; + } + + if(index != dohlen) + return DOH_DNS_MALFORMAT; /* something is wrong */ + + if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr) + /* nothing stored! */ + return DOH_NO_CONTENT; + + return DOH_OK; /* ok */ +} + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void showdoh(struct Curl_easy *data, + const struct dohentry *d) +{ + int i; + infof(data, "TTL: %u seconds", d->ttl); + for(i = 0; i < d->numaddr; i++) { + const struct dohaddr *a = &d->addr[i]; + if(a->type == 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 == DNS_TYPE_AAAA) { + int j; + char buffer[128]; + char *ptr; + size_t len; + msnprintf(buffer, 128, "DoH AAAA: "); + ptr = &buffer[10]; + len = 118; + for(j = 0; j < 16; j += 2) { + size_t l; + 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); + } + } + for(i = 0; i < d->numcname; i++) { + infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i])); + } +} +#else +#define showdoh(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 + * a IPv6 stack, but usable also for IPv4, all hosts and environments. + * + * The memory allocated by this function *MUST* be free'd later on calling + * 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) +{ + struct Curl_addrinfo *ai; + struct Curl_addrinfo *prevai = NULL; + struct Curl_addrinfo *firstai = NULL; + struct sockaddr_in *addr; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *addr6; +#endif + 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; + CURL_SA_FAMILY_T addrtype; + if(de->addr[i].type == DNS_TYPE_AAAA) { +#ifndef ENABLE_IPV6 + /* we can't handle IPv6 addresses */ + continue; +#else + ss_size = sizeof(struct sockaddr_in6); + addrtype = AF_INET6; +#endif + } + else { + ss_size = sizeof(struct sockaddr_in); + addrtype = AF_INET; + } + + ai = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen); + if(!ai) { + result = CURLE_OUT_OF_MEMORY; + break; + } + ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo)); + ai->ai_canonname = (void *)((char *)ai->ai_addr + ss_size); + memcpy(ai->ai_canonname, hostname, hostlen); + + if(!firstai) + /* store the pointer we want to return from this function */ + firstai = ai; + + if(prevai) + /* make the previous entry point to this */ + prevai->ai_next = ai; + + ai->ai_family = addrtype; + + /* we return all names as STREAM, so when using this address for TFTP + the type must be ignored and conn->socktype be used instead! */ + ai->ai_socktype = SOCK_STREAM; + + ai->ai_addrlen = (curl_socklen_t)ss_size; + + /* leave the rest of the struct filled with zero */ + + switch(ai->ai_family) { + case AF_INET: + addr = (void *)ai->ai_addr; /* storage area for this info */ + DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4)); + memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr)); + addr->sin_family = addrtype; + addr->sin_port = htons((unsigned short)port); + break; + +#ifdef ENABLE_IPV6 + case AF_INET6: + addr6 = (void *)ai->ai_addr; /* storage area for this info */ + DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6)); + memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr)); + addr6->sin6_family = addrtype; + addr6->sin6_port = htons((unsigned short)port); + break; +#endif + } + + prevai = ai; + } + + if(result) { + Curl_freeaddrinfo(firstai); + firstai = NULL; + } + *aip = firstai; + + return result; +} + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static const char *type2name(DNStype dnstype) +{ + return (dnstype == DNS_TYPE_A)?"A":"AAAA"; +} +#endif + +UNITTEST void de_cleanup(struct dohentry *d) +{ + int i = 0; + for(i = 0; i < d->numcname; i++) { + Curl_dyn_free(&d->cname[i]); + } +} + +CURLcode Curl_doh_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dnsp) +{ + struct connectdata *conn = data->conn; + CURLcode result; + struct dohdata *dohp = data->req.doh; + *dnsp = NULL; /* defaults to no response */ + if(!dohp) + return CURLE_OUT_OF_MEMORY; + + if(!dohp->probe[DOH_PROBE_SLOT_IPADDR_V4].easy && + !dohp->probe[DOH_PROBE_SLOT_IPADDR_V6].easy) { + failf(data, "Could not DoH-resolve: %s", conn->resolve_async.hostname); + return CONN_IS_PROXIED(data->conn)?CURLE_COULDNT_RESOLVE_PROXY: + CURLE_COULDNT_RESOLVE_HOST; + } + else if(!dohp->pending) { + DOHcode rc[DOH_PROBE_SLOTS] = { + DOH_OK, DOH_OK + }; + struct dohentry de; + int slot; + /* remove DoH handles from multi handle and close them */ + for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) { + curl_multi_remove_handle(data->multi, dohp->probe[slot].easy); + Curl_close(&dohp->probe[slot].easy); + } + /* parse the responses, create the struct and return it! */ + de_init(&de); + for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) { + struct dnsprobe *p = &dohp->probe[slot]; + if(!p->dnstype) + continue; + rc[slot] = doh_decode(Curl_dyn_uptr(&p->serverdoh), + Curl_dyn_len(&p->serverdoh), + p->dnstype, + &de); + Curl_dyn_free(&p->serverdoh); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(rc[slot]) { + infof(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]), + type2name(p->dnstype), dohp->host); + } +#endif + } /* next slot */ + + result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */ + if(!rc[DOH_PROBE_SLOT_IPADDR_V4] || !rc[DOH_PROBE_SLOT_IPADDR_V6]) { + /* we have an address, of one kind or other */ + struct Curl_dns_entry *dns; + struct Curl_addrinfo *ai; + + infof(data, "DoH Host name: %s", dohp->host); + showdoh(data, &de); + + result = doh2ai(&de, dohp->host, dohp->port, &ai); + if(result) { + de_cleanup(&de); + return result; + } + + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + /* we got a response, store it in the cache */ + dns = Curl_cache_addr(data, ai, dohp->host, 0, dohp->port); + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + + if(!dns) { + /* returned failure, bail out nicely */ + Curl_freeaddrinfo(ai); + } + else { + conn->resolve_async.dns = dns; + *dnsp = dns; + result = CURLE_OK; /* address resolution OK */ + } + } /* address processing done */ + + /* Now process any build-specific attributes retrieved from DNS */ + + /* All done */ + de_cleanup(&de); + Curl_safefree(data->req.doh); + return result; + + } /* !dohp->pending */ + + /* else wait for pending DoH transactions to complete */ + return CURLE_OK; +} + +#endif /* CURL_DISABLE_DOH */ diff --git a/Utilities/cmcurl/lib/doh.h b/Utilities/cmcurl/lib/doh.h new file mode 100644 index 0000000..7d7b694 --- /dev/null +++ b/Utilities/cmcurl/lib/doh.h @@ -0,0 +1,128 @@ +#ifndef HEADER_CURL_DOH_H +#define HEADER_CURL_DOH_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "urldata.h" +#include "curl_addrinfo.h" + +#ifndef CURL_DISABLE_DOH + +typedef enum { + DOH_OK, + DOH_DNS_BAD_LABEL, /* 1 */ + DOH_DNS_OUT_OF_RANGE, /* 2 */ + DOH_DNS_LABEL_LOOP, /* 3 */ + DOH_TOO_SMALL_BUFFER, /* 4 */ + DOH_OUT_OF_MEM, /* 5 */ + DOH_DNS_RDATA_LEN, /* 6 */ + DOH_DNS_MALFORMAT, /* 7 */ + DOH_DNS_BAD_RCODE, /* 8 - no such name */ + 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 */ +} DOHcode; + +typedef enum { + DNS_TYPE_A = 1, + DNS_TYPE_NS = 2, + DNS_TYPE_CNAME = 5, + DNS_TYPE_AAAA = 28, + DNS_TYPE_DNAME = 39 /* RFC6672 */ +} DNStype; + +/* one of these for each DoH request */ +struct dnsprobe { + CURL *easy; + DNStype dnstype; + unsigned char dohbuffer[512]; + size_t dohlen; + struct dynbuf serverdoh; +}; + +struct dohdata { + struct curl_slist *headers; + struct dnsprobe probe[DOH_PROBE_SLOTS]; + unsigned int pending; /* still outstanding requests */ + int port; + const char *host; +}; + +/* + * Curl_doh() resolve a name using DoH (DNS-over-HTTPS). It resolves a name + * and returns a 'Curl_addrinfo *' with the address information. + */ + +struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp); + +CURLcode Curl_doh_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dns); + +int Curl_doh_getsock(struct connectdata *conn, curl_socket_t *socks); + +#define DOH_MAX_ADDR 24 +#define DOH_MAX_CNAME 4 + +struct dohaddr { + int type; + union { + unsigned char v4[4]; /* network byte order */ + unsigned char v6[16]; + } ip; +}; + +struct dohentry { + struct dynbuf cname[DOH_MAX_CNAME]; + struct dohaddr addr[DOH_MAX_ADDR]; + int numaddr; + unsigned int ttl; + int numcname; +}; + + +#ifdef DEBUGBUILD +DOHcode doh_encode(const char *host, + DNStype dnstype, + unsigned char *dnsp, /* buffer */ + size_t len, /* buffer size */ + size_t *olen); /* output length */ +DOHcode doh_decode(const unsigned char *doh, + size_t dohlen, + DNStype dnstype, + struct dohentry *d); +void de_init(struct dohentry *d); +void de_cleanup(struct dohentry *d); +#endif + +#else /* if DoH is disabled */ +#define Curl_doh(a,b,c,d) NULL +#define Curl_doh_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST +#endif + +#endif /* HEADER_CURL_DOH_H */ diff --git a/Utilities/cmcurl/lib/dynbuf.c b/Utilities/cmcurl/lib/dynbuf.c new file mode 100644 index 0000000..2973d8d --- /dev/null +++ b/Utilities/cmcurl/lib/dynbuf.c @@ -0,0 +1,279 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "dynbuf.h" +#include "curl_printf.h" +#ifdef BUILDING_LIBCURL +#include "curl_memory.h" +#endif +#include "memdebug.h" + +#define MIN_FIRST_ALLOC 32 + +#define DYNINIT 0xbee51da /* random pattern */ + +/* + * Init a dynbuf struct. + */ +void Curl_dyn_init(struct dynbuf *s, size_t toobig) +{ + DEBUGASSERT(s); + DEBUGASSERT(toobig); + s->bufr = NULL; + s->leng = 0; + s->allc = 0; + s->toobig = toobig; +#ifdef DEBUGBUILD + s->init = DYNINIT; +#endif +} + +/* + * free the buffer and re-init the necessary fields. It doesn't touch the + * 'init' field and thus this buffer can be reused to add data to again. + */ +void Curl_dyn_free(struct dynbuf *s) +{ + DEBUGASSERT(s); + Curl_safefree(s->bufr); + s->leng = s->allc = 0; +} + +/* + * Store/append an chunk of memory to the dynbuf. + */ +static CURLcode dyn_nappend(struct dynbuf *s, + const unsigned char *mem, size_t len) +{ + size_t indx = s->leng; + size_t a = s->allc; + size_t fit = len + indx + 1; /* new string + old string + zero byte */ + + /* try to detect if there's rubbish in the struct */ + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(s->toobig); + DEBUGASSERT(indx < s->toobig); + DEBUGASSERT(!s->leng || s->bufr); + DEBUGASSERT(a <= s->toobig); + DEBUGASSERT(!len || mem); + + if(fit > s->toobig) { + Curl_dyn_free(s); + return CURLE_OUT_OF_MEMORY; + } + else if(!a) { + DEBUGASSERT(!indx); + /* first invoke */ + if(MIN_FIRST_ALLOC > s->toobig) + a = s->toobig; + else if(fit < MIN_FIRST_ALLOC) + a = MIN_FIRST_ALLOC; + else + a = fit; + } + else { + while(a < fit) + a *= 2; + if(a > s->toobig) + /* no point in allocating a larger buffer than this is allowed to use */ + a = s->toobig; + } + + if(a != s->allc) { + /* this logic is not using Curl_saferealloc() to make the tool not have to + include that as well when it uses this code */ + void *p = realloc(s->bufr, a); + if(!p) { + Curl_dyn_free(s); + return CURLE_OUT_OF_MEMORY; + } + s->bufr = p; + s->allc = a; + } + + if(len) + memcpy(&s->bufr[indx], mem, len); + s->leng = indx + len; + s->bufr[s->leng] = 0; + return CURLE_OK; +} + +/* + * Clears the string, keeps the allocation. This can also be called on a + * buffer that already was freed. + */ +void Curl_dyn_reset(struct dynbuf *s) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + if(s->leng) + s->bufr[0] = 0; + s->leng = 0; +} + +/* + * Specify the size of the tail to keep (number of bytes from the end of the + * buffer). The rest will be dropped. + */ +CURLcode Curl_dyn_tail(struct dynbuf *s, size_t trail) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + if(trail > s->leng) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(trail == s->leng) + return CURLE_OK; + else if(!trail) { + Curl_dyn_reset(s); + } + else { + memmove(&s->bufr[0], &s->bufr[s->leng - trail], trail); + s->leng = trail; + s->bufr[s->leng] = 0; + } + return CURLE_OK; + +} + +/* + * Appends a buffer with length. + */ +CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + return dyn_nappend(s, mem, len); +} + +/* + * Append a null-terminated string at the end. + */ +CURLcode Curl_dyn_add(struct dynbuf *s, const char *str) +{ + size_t n; + DEBUGASSERT(str); + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + n = strlen(str); + return dyn_nappend(s, (unsigned char *)str, n); +} + +/* + * Append a string vprintf()-style + */ +CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) +{ +#ifdef BUILDING_LIBCURL + int rc; + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + DEBUGASSERT(fmt); + rc = Curl_dyn_vprintf(s, fmt, ap); + + if(!rc) + return CURLE_OK; +#else + char *str; + str = vaprintf(fmt, ap); /* this allocs a new string to append */ + + if(str) { + CURLcode result = dyn_nappend(s, (unsigned char *)str, strlen(str)); + free(str); + return result; + } + /* If we failed, we cleanup the whole buffer and return error */ + Curl_dyn_free(s); +#endif + return CURLE_OUT_OF_MEMORY; +} + +/* + * Append a string printf()-style + */ +CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...) +{ + CURLcode result; + va_list ap; + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + va_start(ap, fmt); + result = Curl_dyn_vaddf(s, fmt, ap); + va_end(ap); + return result; +} + +/* + * Returns a pointer to the buffer. + */ +char *Curl_dyn_ptr(const struct dynbuf *s) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + return s->bufr; +} + +/* + * Returns an unsigned pointer to the buffer. + */ +unsigned char *Curl_dyn_uptr(const struct dynbuf *s) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + return (unsigned char *)s->bufr; +} + +/* + * Returns the length of the buffer. + */ +size_t Curl_dyn_len(const struct dynbuf *s) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + return s->leng; +} + +/* + * Set a new (smaller) length. + */ +CURLcode Curl_dyn_setlen(struct dynbuf *s, size_t set) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + if(set > s->leng) + return CURLE_BAD_FUNCTION_ARGUMENT; + s->leng = set; + s->bufr[s->leng] = 0; + return CURLE_OK; +} diff --git a/Utilities/cmcurl/lib/dynbuf.h b/Utilities/cmcurl/lib/dynbuf.h new file mode 100644 index 0000000..31a9130 --- /dev/null +++ b/Utilities/cmcurl/lib/dynbuf.h @@ -0,0 +1,93 @@ +#ifndef HEADER_CURL_DYNBUF_H +#define HEADER_CURL_DYNBUF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include <curl/curl.h> + +#ifndef BUILDING_LIBCURL +/* this renames the functions so that the tool code can use the same code + without getting symbol collisions */ +#define Curl_dyn_init(a,b) curlx_dyn_init(a,b) +#define Curl_dyn_add(a,b) curlx_dyn_add(a,b) +#define Curl_dyn_addn(a,b,c) curlx_dyn_addn(a,b,c) +#define Curl_dyn_addf curlx_dyn_addf +#define Curl_dyn_vaddf curlx_dyn_vaddf +#define Curl_dyn_free(a) curlx_dyn_free(a) +#define Curl_dyn_ptr(a) curlx_dyn_ptr(a) +#define Curl_dyn_uptr(a) curlx_dyn_uptr(a) +#define Curl_dyn_len(a) curlx_dyn_len(a) +#define Curl_dyn_reset(a) curlx_dyn_reset(a) +#define Curl_dyn_tail(a,b) curlx_dyn_tail(a,b) +#define Curl_dyn_setlen(a,b) curlx_dyn_setlen(a,b) +#define curlx_dynbuf dynbuf /* for the struct name */ +#endif + +struct dynbuf { + char *bufr; /* point to a null-terminated allocated buffer */ + size_t leng; /* number of bytes *EXCLUDING* the null-terminator */ + size_t allc; /* size of the current allocation */ + size_t toobig; /* size limit for the buffer */ +#ifdef DEBUGBUILD + int init; /* detect API usage mistakes */ +#endif +}; + +void Curl_dyn_init(struct dynbuf *s, size_t toobig); +void Curl_dyn_free(struct dynbuf *s); +CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len) + WARN_UNUSED_RESULT; +CURLcode Curl_dyn_add(struct dynbuf *s, const char *str) + WARN_UNUSED_RESULT; +CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...) + WARN_UNUSED_RESULT; +CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) + WARN_UNUSED_RESULT; +void Curl_dyn_reset(struct dynbuf *s); +CURLcode Curl_dyn_tail(struct dynbuf *s, size_t trail); +CURLcode Curl_dyn_setlen(struct dynbuf *s, size_t set); +char *Curl_dyn_ptr(const struct dynbuf *s); +unsigned char *Curl_dyn_uptr(const struct dynbuf *s); +size_t Curl_dyn_len(const struct dynbuf *s); + +/* returns 0 on success, -1 on error */ +/* The implementation of this function exists in mprintf.c */ +int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save); + +/* Dynamic buffer max sizes */ +#define DYN_DOH_RESPONSE 3000 +#define DYN_DOH_CNAME 256 +#define DYN_PAUSE_BUFFER (64 * 1024 * 1024) +#define DYN_HAXPROXY 2048 +#define DYN_HTTP_REQUEST (1024*1024) +#define DYN_APRINTF 8000000 +#define DYN_RTSP_REQ_HEADER (64*1024) +#define DYN_TRAILERS (64*1024) +#define DYN_PROXY_CONNECT_HEADERS 16384 +#define DYN_QLOG_NAME 1024 +#define DYN_H1_TRAILER 4096 +#define DYN_PINGPPONG_CMD (64*1024) +#define DYN_IMAP_CMD (64*1024) +#define DYN_MQTT_RECV (64*1024) +#endif diff --git a/Utilities/cmcurl/lib/dynhds.c b/Utilities/cmcurl/lib/dynhds.c new file mode 100644 index 0000000..d754895 --- /dev/null +++ b/Utilities/cmcurl/lib/dynhds.c @@ -0,0 +1,396 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "dynhds.h" +#include "strcase.h" + +/* The last 3 #include files should be in this order */ +#ifdef USE_NGHTTP2 +#include <stdint.h> +#include <nghttp2/nghttp2.h> +#endif /* USE_NGHTTP2 */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +static struct dynhds_entry * +entry_new(const char *name, size_t namelen, + const char *value, size_t valuelen, int opts) +{ + struct dynhds_entry *e; + char *p; + + DEBUGASSERT(name); + DEBUGASSERT(value); + e = calloc(1, sizeof(*e) + namelen + valuelen + 2); + if(!e) + return NULL; + e->name = p = ((char *)e) + sizeof(*e); + memcpy(p, name, namelen); + e->namelen = namelen; + e->value = p += namelen + 1; /* leave a \0 at the end of name */ + memcpy(p, value, valuelen); + e->valuelen = valuelen; + if(opts & DYNHDS_OPT_LOWERCASE) + Curl_strntolower(e->name, e->name, e->namelen); + return e; +} + +static struct dynhds_entry * +entry_append(struct dynhds_entry *e, + const char *value, size_t valuelen) +{ + struct dynhds_entry *e2; + size_t valuelen2 = e->valuelen + 1 + valuelen; + char *p; + + DEBUGASSERT(value); + e2 = calloc(1, sizeof(*e) + e->namelen + valuelen2 + 2); + if(!e2) + return NULL; + e2->name = p = ((char *)e2) + sizeof(*e2); + memcpy(p, e->name, e->namelen); + e2->namelen = e->namelen; + e2->value = p += e->namelen + 1; /* leave a \0 at the end of name */ + memcpy(p, e->value, e->valuelen); + p += e->valuelen; + p[0] = ' '; + memcpy(p + 1, value, valuelen); + e2->valuelen = valuelen2; + return e2; +} + +static void entry_free(struct dynhds_entry *e) +{ + free(e); +} + +void Curl_dynhds_init(struct dynhds *dynhds, size_t max_entries, + size_t max_strs_size) +{ + DEBUGASSERT(dynhds); + DEBUGASSERT(max_strs_size); + dynhds->hds = NULL; + dynhds->hds_len = dynhds->hds_allc = dynhds->strs_len = 0; + dynhds->max_entries = max_entries; + dynhds->max_strs_size = max_strs_size; + dynhds->opts = 0; +} + +void Curl_dynhds_free(struct dynhds *dynhds) +{ + DEBUGASSERT(dynhds); + if(dynhds->hds && dynhds->hds_len) { + size_t i; + DEBUGASSERT(dynhds->hds); + for(i = 0; i < dynhds->hds_len; ++i) { + entry_free(dynhds->hds[i]); + } + } + Curl_safefree(dynhds->hds); + dynhds->hds_len = dynhds->hds_allc = dynhds->strs_len = 0; +} + +void Curl_dynhds_reset(struct dynhds *dynhds) +{ + DEBUGASSERT(dynhds); + if(dynhds->hds_len) { + size_t i; + DEBUGASSERT(dynhds->hds); + for(i = 0; i < dynhds->hds_len; ++i) { + entry_free(dynhds->hds[i]); + dynhds->hds[i] = NULL; + } + } + dynhds->hds_len = dynhds->strs_len = 0; +} + +size_t Curl_dynhds_count(struct dynhds *dynhds) +{ + return dynhds->hds_len; +} + +void Curl_dynhds_set_opts(struct dynhds *dynhds, int opts) +{ + dynhds->opts = opts; +} + +struct dynhds_entry *Curl_dynhds_getn(struct dynhds *dynhds, size_t n) +{ + DEBUGASSERT(dynhds); + return (n < dynhds->hds_len)? dynhds->hds[n] : NULL; +} + +struct dynhds_entry *Curl_dynhds_get(struct dynhds *dynhds, const char *name, + size_t namelen) +{ + size_t i; + for(i = 0; i < dynhds->hds_len; ++i) { + if(dynhds->hds[i]->namelen == namelen && + strncasecompare(dynhds->hds[i]->name, name, namelen)) { + return dynhds->hds[i]; + } + } + return NULL; +} + +struct dynhds_entry *Curl_dynhds_cget(struct dynhds *dynhds, const char *name) +{ + return Curl_dynhds_get(dynhds, name, strlen(name)); +} + +CURLcode Curl_dynhds_add(struct dynhds *dynhds, + const char *name, size_t namelen, + const char *value, size_t valuelen) +{ + struct dynhds_entry *entry = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; + + DEBUGASSERT(dynhds); + if(dynhds->max_entries && dynhds->hds_len >= dynhds->max_entries) + return CURLE_OUT_OF_MEMORY; + if(dynhds->strs_len + namelen + valuelen > dynhds->max_strs_size) + return CURLE_OUT_OF_MEMORY; + +entry = entry_new(name, namelen, value, valuelen, dynhds->opts); + if(!entry) + goto out; + + if(dynhds->hds_len + 1 >= dynhds->hds_allc) { + size_t nallc = dynhds->hds_len + 16; + struct dynhds_entry **nhds; + + if(dynhds->max_entries && nallc > dynhds->max_entries) + nallc = dynhds->max_entries; + + nhds = calloc(nallc, sizeof(struct dynhds_entry *)); + if(!nhds) + goto out; + if(dynhds->hds) { + memcpy(nhds, dynhds->hds, + dynhds->hds_len * sizeof(struct dynhds_entry *)); + Curl_safefree(dynhds->hds); + } + dynhds->hds = nhds; + dynhds->hds_allc = nallc; + } + dynhds->hds[dynhds->hds_len++] = entry; + entry = NULL; + dynhds->strs_len += namelen + valuelen; + result = CURLE_OK; + +out: + if(entry) + entry_free(entry); + return result; +} + +CURLcode Curl_dynhds_cadd(struct dynhds *dynhds, + const char *name, const char *value) +{ + return Curl_dynhds_add(dynhds, name, strlen(name), value, strlen(value)); +} + +CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, + const char *line, size_t line_len) +{ + const char *p; + const char *name; + size_t namelen; + const char *value; + size_t valuelen, i; + + if(!line || !line_len) + return CURLE_OK; + + if((line[0] == ' ') || (line[0] == '\t')) { + struct dynhds_entry *e, *e2; + /* header continuation, yikes! */ + if(!dynhds->hds_len) + return CURLE_BAD_FUNCTION_ARGUMENT; + + while(line_len && ISBLANK(line[0])) { + ++line; + --line_len; + } + if(!line_len) + return CURLE_BAD_FUNCTION_ARGUMENT; + e = dynhds->hds[dynhds->hds_len-1]; + e2 = entry_append(e, line, line_len); + if(!e2) + return CURLE_OUT_OF_MEMORY; + dynhds->hds[dynhds->hds_len-1] = e2; + entry_free(e); + return CURLE_OK; + } + else { + p = memchr(line, ':', line_len); + if(!p) + return CURLE_BAD_FUNCTION_ARGUMENT; + name = line; + namelen = p - line; + p++; /* move past the colon */ + for(i = namelen + 1; i < line_len; ++i, ++p) { + if(!ISBLANK(*p)) + break; + } + value = p; + valuelen = line_len - i; + + p = memchr(value, '\r', valuelen); + if(!p) + p = memchr(value, '\n', valuelen); + if(p) + valuelen = (size_t)(p - value); + + return Curl_dynhds_add(dynhds, name, namelen, value, valuelen); + } +} + +CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line) +{ + return Curl_dynhds_h1_add_line(dynhds, line, line? strlen(line) : 0); +} + +#ifdef DEBUGBUILD +/* used by unit2602.c */ + +bool Curl_dynhds_contains(struct dynhds *dynhds, + const char *name, size_t namelen) +{ + return !!Curl_dynhds_get(dynhds, name, namelen); +} + +bool Curl_dynhds_ccontains(struct dynhds *dynhds, const char *name) +{ + return Curl_dynhds_contains(dynhds, name, strlen(name)); +} + +size_t Curl_dynhds_count_name(struct dynhds *dynhds, + const char *name, size_t namelen) +{ + size_t n = 0; + if(dynhds->hds_len) { + size_t i; + for(i = 0; i < dynhds->hds_len; ++i) { + if((namelen == dynhds->hds[i]->namelen) && + strncasecompare(name, dynhds->hds[i]->name, namelen)) + ++n; + } + } + return n; +} + +size_t Curl_dynhds_ccount_name(struct dynhds *dynhds, const char *name) +{ + return Curl_dynhds_count_name(dynhds, name, strlen(name)); +} + +CURLcode Curl_dynhds_set(struct dynhds *dynhds, + const char *name, size_t namelen, + const char *value, size_t valuelen) +{ + Curl_dynhds_remove(dynhds, name, namelen); + return Curl_dynhds_add(dynhds, name, namelen, value, valuelen); +} + +size_t Curl_dynhds_remove(struct dynhds *dynhds, + const char *name, size_t namelen) +{ + size_t n = 0; + if(dynhds->hds_len) { + size_t i, len; + for(i = 0; i < dynhds->hds_len; ++i) { + if((namelen == dynhds->hds[i]->namelen) && + strncasecompare(name, dynhds->hds[i]->name, namelen)) { + ++n; + --dynhds->hds_len; + dynhds->strs_len -= (dynhds->hds[i]->namelen + + dynhds->hds[i]->valuelen); + entry_free(dynhds->hds[i]); + len = dynhds->hds_len - i; /* remaining entries */ + if(len) { + memmove(&dynhds->hds[i], &dynhds->hds[i + 1], + len * sizeof(dynhds->hds[i])); + } + --i; /* do this index again */ + } + } + } + return n; +} + +size_t Curl_dynhds_cremove(struct dynhds *dynhds, const char *name) +{ + return Curl_dynhds_remove(dynhds, name, strlen(name)); +} + +#endif + +CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf) +{ + CURLcode result = CURLE_OK; + size_t i; + + if(!dynhds->hds_len) + return result; + + for(i = 0; i < dynhds->hds_len; ++i) { + result = Curl_dyn_addf(dbuf, "%.*s: %.*s\r\n", + (int)dynhds->hds[i]->namelen, dynhds->hds[i]->name, + (int)dynhds->hds[i]->valuelen, dynhds->hds[i]->value); + if(result) + break; + } + + return result; +} + +#ifdef USE_NGHTTP2 + +nghttp2_nv *Curl_dynhds_to_nva(struct dynhds *dynhds, size_t *pcount) +{ + nghttp2_nv *nva = calloc(1, sizeof(nghttp2_nv) * dynhds->hds_len); + size_t i; + + *pcount = 0; + if(!nva) + return NULL; + + for(i = 0; i < dynhds->hds_len; ++i) { + struct dynhds_entry *e = dynhds->hds[i]; + DEBUGASSERT(e); + nva[i].name = (unsigned char *)e->name; + nva[i].namelen = e->namelen; + nva[i].value = (unsigned char *)e->value; + nva[i].valuelen = e->valuelen; + nva[i].flags = NGHTTP2_NV_FLAG_NONE; + } + *pcount = dynhds->hds_len; + return nva; +} + +#endif /* USE_NGHTTP2 */ diff --git a/Utilities/cmcurl/lib/dynhds.h b/Utilities/cmcurl/lib/dynhds.h new file mode 100644 index 0000000..3b53600 --- /dev/null +++ b/Utilities/cmcurl/lib/dynhds.h @@ -0,0 +1,183 @@ +#ifndef HEADER_CURL_DYNHDS_H +#define HEADER_CURL_DYNHDS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#include <curl/curl.h> +#include "dynbuf.h" + +struct dynbuf; + +/** + * A single header entry. + * `name` and `value` are non-NULL and always NUL terminated. + */ +struct dynhds_entry { + char *name; + char *value; + size_t namelen; + size_t valuelen; +}; + +struct dynhds { + struct dynhds_entry **hds; + size_t hds_len; /* number of entries in hds */ + size_t hds_allc; /* size of hds allocation */ + size_t max_entries; /* size limit number of entries */ + size_t strs_len; /* length of all strings */ + size_t max_strs_size; /* max length of all strings */ + int opts; +}; + +#define DYNHDS_OPT_NONE (0) +#define DYNHDS_OPT_LOWERCASE (1 << 0) + +/** + * Init for use on first time or after a reset. + * Allow `max_entries` headers to be added, 0 for unlimited. + * Allow size of all name and values added to not exceed `max_strs_size`` + */ +void Curl_dynhds_init(struct dynhds *dynhds, size_t max_entries, + size_t max_strs_size); +/** + * Frees all data held in `dynhds`, but not the struct itself. + */ +void Curl_dynhds_free(struct dynhds *dynhds); + +/** + * Reset `dyndns` to the initial init state. May keep allocations + * around. + */ +void Curl_dynhds_reset(struct dynhds *dynhds); + +/** + * Return the number of header entries. + */ +size_t Curl_dynhds_count(struct dynhds *dynhds); + +/** + * Set the options to use, replacing any existing ones. + * This will not have an effect on already existing headers. + */ +void Curl_dynhds_set_opts(struct dynhds *dynhds, int opts); + +/** + * Return the n-th header entry or NULL if it does not exist. + */ +struct dynhds_entry *Curl_dynhds_getn(struct dynhds *dynhds, size_t n); + +/** + * Return the 1st header entry of the name or NULL if none exists. + */ +struct dynhds_entry *Curl_dynhds_get(struct dynhds *dynhds, + const char *name, size_t namelen); +struct dynhds_entry *Curl_dynhds_cget(struct dynhds *dynhds, const char *name); + +/** + * Return TRUE iff one or more headers with the given name exist. + */ +bool Curl_dynhds_contains(struct dynhds *dynhds, + const char *name, size_t namelen); +bool Curl_dynhds_ccontains(struct dynhds *dynhds, const char *name); + +/** + * Return how often the given name appears in `dynhds`. + * Names are case-insensitive. + */ +size_t Curl_dynhds_count_name(struct dynhds *dynhds, + const char *name, size_t namelen); + +/** + * Return how often the given 0-terminated name appears in `dynhds`. + * Names are case-insensitive. + */ +size_t Curl_dynhds_ccount_name(struct dynhds *dynhds, const char *name); + +/** + * Add a header, name + value, to `dynhds` at the end. Does *not* + * check for duplicate names. + */ +CURLcode Curl_dynhds_add(struct dynhds *dynhds, + const char *name, size_t namelen, + const char *value, size_t valuelen); + +/** + * Add a header, c-string name + value, to `dynhds` at the end. + */ +CURLcode Curl_dynhds_cadd(struct dynhds *dynhds, + const char *name, const char *value); + +/** + * Remove all entries with the given name. + * Returns number of entries removed. + */ +size_t Curl_dynhds_remove(struct dynhds *dynhds, + const char *name, size_t namelen); +size_t Curl_dynhds_cremove(struct dynhds *dynhds, const char *name); + + +/** + * Set the give header name and value, replacing any entries with + * the same name. The header is added at the end of all (remaining) + * entries. + */ +CURLcode Curl_dynhds_set(struct dynhds *dynhds, + const char *name, size_t namelen, + const char *value, size_t valuelen); + +CURLcode Curl_dynhds_cset(struct dynhds *dynhds, + const char *name, const char *value); + +/** + * Add a single header from a HTTP/1.1 formatted line at the end. Line + * may contain a delimiting \r\n or just \n. Any characters after + * that will be ignored. + */ +CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line); + +/** + * Add a single header from a HTTP/1.1 formatted line at the end. Line + * may contain a delimiting \r\n or just \n. Any characters after + * that will be ignored. + */ +CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, + const char *line, size_t line_len); + +/** + * Add the headers to the given `dynbuf` in HTTP/1.1 format with + * cr+lf line endings. Will NOT output a last empty line. + */ +CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf); + +#ifdef USE_NGHTTP2 + +#include <stdint.h> +#include <nghttp2/nghttp2.h> + +nghttp2_nv *Curl_dynhds_to_nva(struct dynhds *dynhds, size_t *pcount); + +#endif /* USE_NGHTTP2 */ + +#endif /* HEADER_CURL_DYNHDS_H */ diff --git a/Utilities/cmcurl/lib/easy.c b/Utilities/cmcurl/lib/easy.c new file mode 100644 index 0000000..322d1a4 --- /dev/null +++ b/Utilities/cmcurl/lib/easy.c @@ -0,0 +1,1327 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#include "urldata.h" +#include <curl/curl.h> +#include "transfer.h" +#include "vtls/vtls.h" +#include "url.h" +#include "getinfo.h" +#include "hostip.h" +#include "share.h" +#include "strdup.h" +#include "progress.h" +#include "easyif.h" +#include "multiif.h" +#include "select.h" +#include "cfilters.h" +#include "sendf.h" /* for failf function prototype */ +#include "connect.h" /* for Curl_getconnectinfo */ +#include "slist.h" +#include "mime.h" +#include "amigaos.h" +#include "macos.h" +#include "warnless.h" +#include "sigpipe.h" +#include "vssh/ssh.h" +#include "setopt.h" +#include "http_digest.h" +#include "system_win32.h" +#include "http2.h" +#include "dynbuf.h" +#include "altsvc.h" +#include "hsts.h" + +#include "easy_lock.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* true globals -- for curl_global_init() and curl_global_cleanup() */ +static unsigned int initialized; +static long easy_init_flags; + +#ifdef GLOBAL_INIT_IS_THREADSAFE + +static curl_simple_lock s_lock = CURL_SIMPLE_LOCK_INIT; +#define global_init_lock() curl_simple_lock_lock(&s_lock) +#define global_init_unlock() curl_simple_lock_unlock(&s_lock) + +#else + +#define global_init_lock() +#define global_init_unlock() + +#endif + +/* + * strdup (and other memory functions) is redefined in complicated + * ways, but at this point it must be defined as the system-supplied strdup + * so the callback pointer is initialized correctly. + */ +#if defined(_WIN32_WCE) +#define system_strdup _strdup +#elif !defined(HAVE_STRDUP) +#define system_strdup Curl_strdup +#else +#define system_strdup strdup +#endif + +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(disable:4232) /* MSVC extension, dllimport identity */ +#endif + +/* + * If a memory-using function (like curl_getenv) is used before + * curl_global_init() is called, we need to have these pointers set already. + */ +curl_malloc_callback Curl_cmalloc = (curl_malloc_callback)malloc; +curl_free_callback Curl_cfree = (curl_free_callback)free; +curl_realloc_callback Curl_crealloc = (curl_realloc_callback)realloc; +curl_strdup_callback Curl_cstrdup = (curl_strdup_callback)system_strdup; +curl_calloc_callback Curl_ccalloc = (curl_calloc_callback)calloc; +#if defined(_WIN32) && defined(UNICODE) +curl_wcsdup_callback Curl_cwcsdup = Curl_wcsdup; +#endif + +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(default:4232) /* MSVC extension, dllimport identity */ +#endif + +#ifdef DEBUGBUILD +static char *leakpointer; +#endif + +/** + * curl_global_init() globally initializes curl given a bitwise set of the + * different features of what to initialize. + */ +static CURLcode global_init(long flags, bool memoryfuncs) +{ + if(initialized++) + return CURLE_OK; + + if(memoryfuncs) { + /* Setup the default memory functions here (again) */ + Curl_cmalloc = (curl_malloc_callback)malloc; + Curl_cfree = (curl_free_callback)free; + Curl_crealloc = (curl_realloc_callback)realloc; + Curl_cstrdup = (curl_strdup_callback)system_strdup; + Curl_ccalloc = (curl_calloc_callback)calloc; +#if defined(_WIN32) && defined(UNICODE) + Curl_cwcsdup = (curl_wcsdup_callback)_wcsdup; +#endif + } + + if(Curl_trc_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_trc_init failed\n")); + goto fail; + } + + if(!Curl_ssl_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_ssl_init failed\n")); + goto fail; + } + + if(Curl_win32_init(flags)) { + DEBUGF(fprintf(stderr, "Error: win32_init failed\n")); + goto fail; + } + + if(Curl_amiga_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_amiga_init failed\n")); + goto fail; + } + + if(Curl_macos_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_macos_init failed\n")); + goto fail; + } + + if(Curl_resolver_global_init()) { + DEBUGF(fprintf(stderr, "Error: resolver_global_init failed\n")); + goto fail; + } + + if(Curl_ssh_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_ssh_init failed\n")); + goto fail; + } + + easy_init_flags = flags; + +#ifdef DEBUGBUILD + if(getenv("CURL_GLOBAL_INIT")) + /* alloc data that will leak if *cleanup() is not called! */ + leakpointer = malloc(1); +#endif + + return CURLE_OK; + +fail: + initialized--; /* undo the increase */ + return CURLE_FAILED_INIT; +} + + +/** + * curl_global_init() globally initializes curl given a bitwise set of the + * different features of what to initialize. + */ +CURLcode curl_global_init(long flags) +{ + CURLcode result; + global_init_lock(); + + result = global_init(flags, TRUE); + + global_init_unlock(); + + return result; +} + +/* + * curl_global_init_mem() globally initializes curl and also registers the + * user provided callback routines. + */ +CURLcode curl_global_init_mem(long flags, curl_malloc_callback m, + curl_free_callback f, curl_realloc_callback r, + curl_strdup_callback s, curl_calloc_callback c) +{ + CURLcode result; + + /* Invalid input, return immediately */ + if(!m || !f || !r || !s || !c) + return CURLE_FAILED_INIT; + + global_init_lock(); + + if(initialized) { + /* Already initialized, don't do it again, but bump the variable anyway to + work like curl_global_init() and require the same amount of cleanup + calls. */ + initialized++; + global_init_unlock(); + return CURLE_OK; + } + + /* set memory functions before global_init() in case it wants memory + functions */ + Curl_cmalloc = m; + Curl_cfree = f; + Curl_cstrdup = s; + Curl_crealloc = r; + Curl_ccalloc = c; + + /* Call the actual init function, but without setting */ + result = global_init(flags, FALSE); + + global_init_unlock(); + + return result; +} + +/** + * curl_global_cleanup() globally cleanups curl, uses the value of + * "easy_init_flags" to determine what needs to be cleaned up and what doesn't. + */ +void curl_global_cleanup(void) +{ + global_init_lock(); + + if(!initialized) { + global_init_unlock(); + return; + } + + if(--initialized) { + global_init_unlock(); + return; + } + + Curl_ssl_cleanup(); + Curl_resolver_global_cleanup(); + +#ifdef _WIN32 + Curl_win32_cleanup(easy_init_flags); +#endif + + Curl_amiga_cleanup(); + + Curl_ssh_cleanup(); + +#ifdef DEBUGBUILD + free(leakpointer); +#endif + + easy_init_flags = 0; + + global_init_unlock(); +} + +/** + * curl_global_trace() globally initializes curl logging. + */ +CURLcode curl_global_trace(const char *config) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + CURLcode result; + global_init_lock(); + + result = Curl_trc_opt(config); + + global_init_unlock(); + + return result; +#else + (void)config; + return CURLE_OK; +#endif +} + +/* + * curl_global_sslset() globally initializes the SSL backend to use. + */ +CURLsslset curl_global_sslset(curl_sslbackend id, const char *name, + const curl_ssl_backend ***avail) +{ + CURLsslset rc; + + global_init_lock(); + + rc = Curl_init_sslset_nolock(id, name, avail); + + global_init_unlock(); + + return rc; +} + +/* + * curl_easy_init() is the external interface to alloc, setup and init an + * easy handle that is returned. If anything goes wrong, NULL is returned. + */ +struct Curl_easy *curl_easy_init(void) +{ + CURLcode result; + struct Curl_easy *data; + + /* Make sure we inited the global SSL stuff */ + global_init_lock(); + + if(!initialized) { + result = global_init(CURL_GLOBAL_DEFAULT, TRUE); + if(result) { + /* something in the global init failed, return nothing */ + DEBUGF(fprintf(stderr, "Error: curl_global_init failed\n")); + global_init_unlock(); + return NULL; + } + } + global_init_unlock(); + + /* We use curl_open() with undefined URL so far */ + result = Curl_open(&data); + if(result) { + DEBUGF(fprintf(stderr, "Error: Curl_open failed\n")); + return NULL; + } + + return data; +} + +#ifdef CURLDEBUG + +struct socketmonitor { + struct socketmonitor *next; /* the next node in the list or NULL */ + struct pollfd socket; /* socket info of what to monitor */ +}; + +struct events { + long ms; /* timeout, run the timeout function when reached */ + bool msbump; /* set TRUE when timeout is set by callback */ + int num_sockets; /* number of nodes in the monitor list */ + struct socketmonitor *list; /* list of sockets to monitor */ + int running_handles; /* store the returned number */ +}; + +/* events_timer + * + * Callback that gets called with a new value when the timeout should be + * updated. + */ + +static int events_timer(struct Curl_multi *multi, /* multi handle */ + long timeout_ms, /* see above */ + void *userp) /* private callback pointer */ +{ + struct events *ev = userp; + (void)multi; + if(timeout_ms == -1) + /* timeout removed */ + timeout_ms = 0; + else if(timeout_ms == 0) + /* timeout is already reached! */ + timeout_ms = 1; /* trigger asap */ + + ev->ms = timeout_ms; + ev->msbump = TRUE; + return 0; +} + + +/* poll2cselect + * + * convert from poll() bit definitions to libcurl's CURL_CSELECT_* ones + */ +static int poll2cselect(int pollmask) +{ + int omask = 0; + if(pollmask & POLLIN) + omask |= CURL_CSELECT_IN; + if(pollmask & POLLOUT) + omask |= CURL_CSELECT_OUT; + if(pollmask & POLLERR) + omask |= CURL_CSELECT_ERR; + return omask; +} + + +/* socketcb2poll + * + * convert from libcurl' CURL_POLL_* bit definitions to poll()'s + */ +static short socketcb2poll(int pollmask) +{ + short omask = 0; + if(pollmask & CURL_POLL_IN) + omask |= POLLIN; + if(pollmask & CURL_POLL_OUT) + omask |= POLLOUT; + return omask; +} + +/* events_socket + * + * Callback that gets called with information about socket activity to + * monitor. + */ +static int events_socket(struct Curl_easy *easy, /* easy handle */ + curl_socket_t s, /* socket */ + int what, /* see above */ + void *userp, /* private callback + pointer */ + void *socketp) /* private socket + pointer */ +{ + struct events *ev = userp; + struct socketmonitor *m; + struct socketmonitor *prev = NULL; + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) easy; +#endif + (void)socketp; + + m = ev->list; + while(m) { + if(m->socket.fd == s) { + + if(what == CURL_POLL_REMOVE) { + struct socketmonitor *nxt = m->next; + /* remove this node from the list of monitored sockets */ + if(prev) + prev->next = nxt; + else + ev->list = nxt; + free(m); + m = nxt; + infof(easy, "socket cb: socket %d REMOVED", s); + } + else { + /* The socket 's' is already being monitored, update the activity + mask. Convert from libcurl bitmask to the poll one. */ + m->socket.events = socketcb2poll(what); + infof(easy, "socket cb: socket %d UPDATED as %s%s", s, + (what&CURL_POLL_IN)?"IN":"", + (what&CURL_POLL_OUT)?"OUT":""); + } + break; + } + prev = m; + m = m->next; /* move to next node */ + } + if(!m) { + if(what == CURL_POLL_REMOVE) { + /* this happens a bit too often, libcurl fix perhaps? */ + /* fprintf(stderr, + "%s: socket %d asked to be REMOVED but not present!\n", + __func__, s); */ + } + else { + m = malloc(sizeof(struct socketmonitor)); + if(m) { + m->next = ev->list; + m->socket.fd = s; + m->socket.events = socketcb2poll(what); + m->socket.revents = 0; + ev->list = m; + infof(easy, "socket cb: socket %d ADDED as %s%s", s, + (what&CURL_POLL_IN)?"IN":"", + (what&CURL_POLL_OUT)?"OUT":""); + } + else + return CURLE_OUT_OF_MEMORY; + } + } + + return 0; +} + + +/* + * events_setup() + * + * Do the multi handle setups that only event-based transfers need. + */ +static void events_setup(struct Curl_multi *multi, struct events *ev) +{ + /* timer callback */ + curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, events_timer); + curl_multi_setopt(multi, CURLMOPT_TIMERDATA, ev); + + /* socket callback */ + curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, events_socket); + curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, ev); +} + + +/* wait_or_timeout() + * + * waits for activity on any of the given sockets, or the timeout to trigger. + */ + +static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) +{ + bool done = FALSE; + CURLMcode mcode = CURLM_OK; + CURLcode result = CURLE_OK; + + while(!done) { + CURLMsg *msg; + struct socketmonitor *m; + struct pollfd *f; + struct pollfd fds[4]; + int numfds = 0; + int pollrc; + int i; + struct curltime before; + struct curltime after; + + /* populate the fds[] array */ + for(m = ev->list, f = &fds[0]; m; m = m->next) { + f->fd = m->socket.fd; + f->events = m->socket.events; + f->revents = 0; + /* fprintf(stderr, "poll() %d check socket %d\n", numfds, f->fd); */ + f++; + numfds++; + } + + /* get the time stamp to use to figure out how long poll takes */ + before = Curl_now(); + + /* wait for activity or timeout */ + pollrc = Curl_poll(fds, numfds, ev->ms); + if(pollrc < 0) + return CURLE_UNRECOVERABLE_POLL; + + after = Curl_now(); + + ev->msbump = FALSE; /* reset here */ + + if(!pollrc) { + /* timeout! */ + ev->ms = 0; + /* fprintf(stderr, "call curl_multi_socket_action(TIMEOUT)\n"); */ + mcode = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, + &ev->running_handles); + } + else { + /* here pollrc is > 0 */ + + /* loop over the monitored sockets to see which ones had activity */ + for(i = 0; i< numfds; i++) { + if(fds[i].revents) { + /* socket activity, tell libcurl */ + int act = poll2cselect(fds[i].revents); /* convert */ + infof(multi->easyp, "call curl_multi_socket_action(socket %d)", + fds[i].fd); + mcode = curl_multi_socket_action(multi, fds[i].fd, act, + &ev->running_handles); + } + } + + if(!ev->msbump) { + /* If nothing updated the timeout, we decrease it by the spent time. + * If it was updated, it has the new timeout time stored already. + */ + timediff_t timediff = Curl_timediff(after, before); + if(timediff > 0) { + if(timediff > ev->ms) + ev->ms = 0; + else + ev->ms -= (long)timediff; + } + } + } + + if(mcode) + return CURLE_URL_MALFORMAT; + + /* we don't really care about the "msgs_in_queue" value returned in the + second argument */ + msg = curl_multi_info_read(multi, &pollrc); + if(msg) { + result = msg->data.result; + done = TRUE; + } + } + + return result; +} + + +/* easy_events() + * + * Runs a transfer in a blocking manner using the events-based API + */ +static CURLcode easy_events(struct Curl_multi *multi) +{ + /* this struct is made static to allow it to be used after this function + returns and curl_multi_remove_handle() is called */ + static struct events evs = {2, FALSE, 0, NULL, 0}; + + /* if running event-based, do some further multi inits */ + events_setup(multi, &evs); + + return wait_or_timeout(multi, &evs); +} +#else /* CURLDEBUG */ +/* when not built with debug, this function doesn't exist */ +#define easy_events(x) CURLE_NOT_BUILT_IN +#endif + +static CURLcode easy_transfer(struct Curl_multi *multi) +{ + bool done = FALSE; + CURLMcode mcode = CURLM_OK; + CURLcode result = CURLE_OK; + + while(!done && !mcode) { + int still_running = 0; + + mcode = curl_multi_poll(multi, NULL, 0, 1000, NULL); + + if(!mcode) + mcode = curl_multi_perform(multi, &still_running); + + /* only read 'still_running' if curl_multi_perform() return OK */ + if(!mcode && !still_running) { + int rc; + CURLMsg *msg = curl_multi_info_read(multi, &rc); + if(msg) { + result = msg->data.result; + done = TRUE; + } + } + } + + /* Make sure to return some kind of error if there was a multi problem */ + if(mcode) { + result = (mcode == CURLM_OUT_OF_MEMORY) ? CURLE_OUT_OF_MEMORY : + /* The other multi errors should never happen, so return + something suitably generic */ + CURLE_BAD_FUNCTION_ARGUMENT; + } + + return result; +} + + +/* + * easy_perform() is the external interface that performs a blocking + * transfer as previously setup. + * + * CONCEPT: This function creates a multi handle, adds the easy handle to it, + * runs curl_multi_perform() until the transfer is done, then detaches the + * easy handle, destroys the multi handle and returns the easy handle's return + * code. + * + * REALITY: it can't just create and destroy the multi handle that easily. It + * needs to keep it around since if this easy handle is used again by this + * function, the same multi handle must be reused so that the same pools and + * caches can be used. + * + * DEBUG: if 'events' is set TRUE, this function will use a replacement engine + * instead of curl_multi_perform() and use curl_multi_socket_action(). + */ +static CURLcode easy_perform(struct Curl_easy *data, bool events) +{ + struct Curl_multi *multi; + CURLMcode mcode; + CURLcode result = CURLE_OK; + SIGPIPE_VARIABLE(pipe_st); + + if(!data) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(data->set.errorbuffer) + /* clear this as early as possible */ + data->set.errorbuffer[0] = 0; + + if(data->multi) { + failf(data, "easy handle already used in multi handle"); + return CURLE_FAILED_INIT; + } + + if(data->multi_easy) + multi = data->multi_easy; + else { + /* this multi handle will only ever have a single easy handled attached + to it, so make it use minimal hashes */ + multi = Curl_multi_handle(1, 3, 7); + if(!multi) + return CURLE_OUT_OF_MEMORY; + data->multi_easy = multi; + } + + if(multi->in_callback) + return CURLE_RECURSIVE_API_CALL; + + /* Copy the MAXCONNECTS option to the multi handle */ + curl_multi_setopt(multi, CURLMOPT_MAXCONNECTS, (long)data->set.maxconnects); + + mcode = curl_multi_add_handle(multi, data); + if(mcode) { + curl_multi_cleanup(multi); + data->multi_easy = NULL; + if(mcode == CURLM_OUT_OF_MEMORY) + return CURLE_OUT_OF_MEMORY; + return CURLE_FAILED_INIT; + } + + sigpipe_ignore(data, &pipe_st); + + /* run the transfer */ + result = events ? easy_events(multi) : easy_transfer(multi); + + /* ignoring the return code isn't nice, but atm we can't really handle + a failure here, room for future improvement! */ + (void)curl_multi_remove_handle(multi, data); + + sigpipe_restore(&pipe_st); + + /* The multi handle is kept alive, owned by the easy handle */ + return result; +} + + +/* + * curl_easy_perform() is the external interface that performs a blocking + * transfer as previously setup. + */ +CURLcode curl_easy_perform(struct Curl_easy *data) +{ + return easy_perform(data, FALSE); +} + +#ifdef CURLDEBUG +/* + * curl_easy_perform_ev() is the external interface that performs a blocking + * transfer using the event-based API internally. + */ +CURLcode curl_easy_perform_ev(struct Curl_easy *data) +{ + return easy_perform(data, TRUE); +} + +#endif + +/* + * curl_easy_cleanup() is the external interface to cleaning/freeing the given + * easy handle. + */ +void curl_easy_cleanup(struct Curl_easy *data) +{ + if(GOOD_EASY_HANDLE(data)) { + SIGPIPE_VARIABLE(pipe_st); + sigpipe_ignore(data, &pipe_st); + Curl_close(&data); + sigpipe_restore(&pipe_st); + } +} + +/* + * curl_easy_getinfo() is an external interface that allows an app to retrieve + * information from a performed transfer and similar. + */ +#undef curl_easy_getinfo +CURLcode curl_easy_getinfo(struct Curl_easy *data, CURLINFO info, ...) +{ + va_list arg; + void *paramp; + CURLcode result; + + va_start(arg, info); + paramp = va_arg(arg, void *); + + result = Curl_getinfo(data, info, paramp); + + va_end(arg); + 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 + afterwards */ + dst->set = src->set; + Curl_mime_initpart(&dst->set.mimepost); + + /* 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; + } + + /* duplicate all blobs */ + for(j = (enum dupblob)0; j < BLOB_LAST; j++) { + result = Curl_setblobopt(&dst->set.blobs[j], src->set.blobs[j]); + if(result) + return result; + } + + /* duplicate memory areas pointed to */ + i = STRING_COPYPOSTFIELDS; + if(src->set.str[i]) { + if(src->set.postfieldsize == -1) + dst->set.str[i] = strdup(src->set.str[i]); + else + /* postfieldsize is curl_off_t, Curl_memdup() takes a size_t ... */ + dst->set.str[i] = Curl_memdup(src->set.str[i], + curlx_sotouz(src->set.postfieldsize)); + if(!dst->set.str[i]) + return CURLE_OUT_OF_MEMORY; + /* point to the new copy */ + dst->set.postfields = dst->set.str[i]; + } + + /* Duplicate mime data. */ + result = Curl_mime_duppart(dst, &dst->set.mimepost, &src->set.mimepost); + + if(src->set.resolve) + dst->state.resolve = dst->set.resolve; + + return result; +} + +/* + * curl_easy_duphandle() is an external interface to allow duplication of a + * given input easy handle. The returned handle will be a new working handle + * with all options set exactly as the input source handle. + */ +struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) +{ + struct Curl_easy *outcurl = calloc(1, sizeof(struct Curl_easy)); + if(!outcurl) + goto fail; + + /* + * 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; + + /* copy all userdefined values */ + if(dupset(outcurl, data)) + goto fail; + + Curl_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); + + /* the connection cache is setup on demand */ + outcurl->state.conn_cache = NULL; + outcurl->state.lastconnect_id = -1; + outcurl->state.recent_conn_id = -1; + outcurl->id = -1; + + outcurl->progress.flags = data->progress.flags; + 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(outcurl, NULL, outcurl->cookies, + data->set.cookiesession); + if(!outcurl->cookies) + goto fail; + } + + if(data->state.cookielist) { + outcurl->state.cookielist = Curl_slist_duplicate(data->state.cookielist); + if(!outcurl->state.cookielist) + goto fail; + } +#endif + + if(data->state.url) { + outcurl->state.url = strdup(data->state.url); + if(!outcurl->state.url) + goto fail; + outcurl->state.url_alloc = TRUE; + } + + if(data->state.referer) { + outcurl->state.referer = strdup(data->state.referer); + if(!outcurl->state.referer) + goto fail; + outcurl->state.referer_alloc = TRUE; + } + + /* 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; + } + +#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]); + } +#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); + } +#endif + + Curl_initinfo(outcurl); + + outcurl->magic = CURLEASY_MAGIC_NUMBER; + + /* we reach this point and thus we are OK */ + + return outcurl; + +fail: + + if(outcurl) { +#ifndef CURL_DISABLE_COOKIES + free(outcurl->cookies); +#endif + free(outcurl->state.buffer); + Curl_dyn_free(&outcurl->state.headerb); + Curl_altsvc_cleanup(&outcurl->asi); + Curl_hsts_cleanup(&outcurl->hsts); + Curl_freeset(outcurl); + free(outcurl); + } + + return NULL; +} + +/* + * curl_easy_reset() is an external interface that allows an app to re- + * initialize a session handle to the default values. + */ +void curl_easy_reset(struct Curl_easy *data) +{ + Curl_free_request_state(data); + + /* zero out UserDefined data: */ + Curl_freeset(data); + memset(&data->set, 0, sizeof(struct UserDefined)); + (void)Curl_init_userdefined(data); + + /* zero out Progress data: */ + memset(&data->progress, 0, sizeof(struct Progress)); + + /* zero out PureInfo data: */ + Curl_initinfo(data); + + data->progress.flags |= PGRS_HIDE; + data->state.current_speed = -1; /* init to negative == impossible */ + data->state.retrycount = 0; /* reset the retry counter */ + + /* 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); +#endif +} + +/* + * curl_easy_pause() allows an application to pause or unpause a specific + * transfer and direction. This function sets the full new state for the + * current connection this easy handle operates on. + * + * NOTE: if you have the receiving paused and you call this function to remove + * the pausing, you may get your write callback called at this point. + * + * Action is a bitmask consisting of CURLPAUSE_* bits in curl/curl.h + * + * NOTE: This is one of few API functions that are allowed to be called from + * within a callback. + */ +CURLcode curl_easy_pause(struct Curl_easy *data, int action) +{ + struct SingleRequest *k; + CURLcode result = CURLE_OK; + int oldstate; + int newstate; + bool recursive = FALSE; + + if(!GOOD_EASY_HANDLE(data) || !data->conn) + /* crazy input, don't continue */ + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(Curl_is_in_callback(data)) + recursive = TRUE; + k = &data->req; + oldstate = k->keepon & (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE); + + /* first switch off both pause bits then set the new pause bits */ + newstate = (k->keepon &~ (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE)) | + ((action & CURLPAUSE_RECV)?KEEP_RECV_PAUSE:0) | + ((action & CURLPAUSE_SEND)?KEEP_SEND_PAUSE:0); + + if((newstate & (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE)) == oldstate) { + /* Not changing any pause state, return */ + DEBUGF(infof(data, "pause: no change, early return")); + return CURLE_OK; + } + + /* Unpause parts in active mime tree. */ + if((k->keepon & ~newstate & KEEP_SEND_PAUSE) && + (data->mstate == MSTATE_PERFORMING || + data->mstate == MSTATE_RATELIMITING) && + data->state.fread_func == (curl_read_callback) Curl_mime_read) { + Curl_mime_unpause(data->state.in); + } + + /* put it back in the keepon */ + k->keepon = newstate; + + if(!(newstate & KEEP_RECV_PAUSE)) { + Curl_conn_ev_data_pause(data, FALSE); + result = Curl_client_unpause(data); + if(result) + return result; + } + +#ifdef USE_HYPER + if(!(newstate & KEEP_SEND_PAUSE)) { + /* need to wake the send body waker */ + if(data->hyp.send_body_waker) { + hyper_waker_wake(data->hyp.send_body_waker); + data->hyp.send_body_waker = NULL; + } + } +#endif + + /* if there's no error and we're not pausing both directions, we want + to have this handle checked soon */ + if((newstate & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) != + (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) { + Curl_expire(data, 0, EXPIRE_RUN_NOW); /* get this handle going again */ + + /* reset the too-slow time keeper */ + data->state.keeps_speed.tv_sec = 0; + + if(!data->state.tempcount) + /* if not pausing again, force a recv/send check of this connection as + the data might've been read off the socket already */ + data->conn->cselect_bits = CURL_CSELECT_IN | CURL_CSELECT_OUT; + if(data->multi) { + if(Curl_update_timer(data->multi)) + return CURLE_ABORTED_BY_CALLBACK; + } + } + + if(!data->state.done) + /* This transfer may have been moved in or out of the bundle, update the + corresponding socket callback, if used */ + result = Curl_updatesocket(data); + + 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); + + return result; +} + + +static CURLcode easy_connection(struct Curl_easy *data, curl_socket_t *sfd, + struct connectdata **connp) +{ + if(!data) + return CURLE_BAD_FUNCTION_ARGUMENT; + + /* only allow these to be called on handles with CURLOPT_CONNECT_ONLY */ + if(!data->set.connect_only) { + failf(data, "CONNECT_ONLY is required"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + + *sfd = Curl_getconnectinfo(data, connp); + + if(*sfd == CURL_SOCKET_BAD) { + failf(data, "Failed to get recent socket"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + + return CURLE_OK; +} + +/* + * Receives data from the connected socket. Use after successful + * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. + * Returns CURLE_OK on success, error code on error. + */ +CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen, + size_t *n) +{ + curl_socket_t sfd; + CURLcode result; + ssize_t n1; + struct connectdata *c; + + if(Curl_is_in_callback(data)) + return CURLE_RECURSIVE_API_CALL; + + result = easy_connection(data, &sfd, &c); + if(result) + return result; + + if(!data->conn) + /* on first invoke, the transfer has been detached from the connection and + needs to be reattached */ + Curl_attach_connection(data, c); + + *n = 0; + result = Curl_read(data, sfd, buffer, buflen, &n1); + + if(result) + return result; + + *n = (size_t)n1; + return CURLE_OK; +} + +#ifdef USE_WEBSOCKETS +CURLcode Curl_connect_only_attach(struct Curl_easy *data) +{ + curl_socket_t sfd; + CURLcode result; + struct connectdata *c = NULL; + + result = easy_connection(data, &sfd, &c); + if(result) + return result; + + if(!data->conn) + /* on first invoke, the transfer has been detached from the connection and + needs to be reattached */ + Curl_attach_connection(data, c); + + return CURLE_OK; +} +#endif /* USE_WEBSOCKETS */ + +/* + * Sends data over the connected socket. + * + * This is the private internal version of curl_easy_send() + */ +CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, + size_t buflen, ssize_t *n) +{ + curl_socket_t sfd; + CURLcode result; + ssize_t n1; + struct connectdata *c = NULL; + SIGPIPE_VARIABLE(pipe_st); + + result = easy_connection(data, &sfd, &c); + if(result) + return result; + + if(!data->conn) + /* on first invoke, the transfer has been detached from the connection and + needs to be reattached */ + Curl_attach_connection(data, c); + + *n = 0; + sigpipe_ignore(data, &pipe_st); + result = Curl_write(data, sfd, buffer, buflen, &n1); + sigpipe_restore(&pipe_st); + + if(n1 == -1) + return CURLE_SEND_ERROR; + + /* detect EAGAIN */ + if(!result && !n1) + return CURLE_AGAIN; + + *n = n1; + + return result; +} + +/* + * Sends data over the connected socket. Use after successful + * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. + */ +CURLcode curl_easy_send(struct Curl_easy *data, const void *buffer, + size_t buflen, size_t *n) +{ + ssize_t written = 0; + CURLcode result; + if(Curl_is_in_callback(data)) + return CURLE_RECURSIVE_API_CALL; + + result = Curl_senddata(data, buffer, buflen, &written); + *n = (size_t)written; + return result; +} + +/* + * Wrapper to call functions in Curl_conncache_foreach() + * + * Returns always 0. + */ +static int conn_upkeep(struct Curl_easy *data, + struct connectdata *conn, + void *param) +{ + struct curltime *now = param; + + if(Curl_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms) + return 0; + + /* briefly attach for action */ + Curl_attach_connection(data, conn); + if(conn->handler->connection_check) { + /* Do a protocol-specific keepalive check on the connection. */ + conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE); + } + else { + /* Do the generic action on the FIRSTSOCKE filter chain */ + Curl_conn_keep_alive(data, conn, FIRSTSOCKET); + } + Curl_detach_connection(data); + + conn->keepalive = *now; + return 0; /* continue iteration */ +} + +static CURLcode upkeep(struct conncache *conn_cache, void *data) +{ + struct curltime now = Curl_now(); + /* Loop over every connection and make connection alive. */ + Curl_conncache_foreach(data, + conn_cache, + &now, + conn_upkeep); + return CURLE_OK; +} + +/* + * Performs connection upkeep for the given session handle. + */ +CURLcode curl_easy_upkeep(struct Curl_easy *data) +{ + /* Verify that we got an easy handle we can work with. */ + if(!GOOD_EASY_HANDLE(data)) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(data->multi_easy) { + /* Use the common function to keep connections alive. */ + return upkeep(&data->multi_easy->conn_cache, data); + } + else { + /* No connections, so just return success */ + return CURLE_OK; + } +} diff --git a/Utilities/cmcurl/lib/easy_lock.h b/Utilities/cmcurl/lib/easy_lock.h new file mode 100644 index 0000000..4f6764d --- /dev/null +++ b/Utilities/cmcurl/lib/easy_lock.h @@ -0,0 +1,111 @@ +#ifndef HEADER_CURL_EASY_LOCK_H +#define HEADER_CURL_EASY_LOCK_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#define GLOBAL_INIT_IS_THREADSAFE + +#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600 + +#ifdef __MINGW32__ +#ifndef SRWLOCK_INIT +#define SRWLOCK_INIT NULL +#endif +#endif /* __MINGW32__ */ + +#define curl_simple_lock SRWLOCK +#define CURL_SIMPLE_LOCK_INIT SRWLOCK_INIT + +#define curl_simple_lock_lock(m) AcquireSRWLockExclusive(m) +#define curl_simple_lock_unlock(m) ReleaseSRWLockExclusive(m) + +#elif defined(HAVE_ATOMIC) && defined(HAVE_STDATOMIC_H) +#include <stdatomic.h> +#if defined(HAVE_SCHED_YIELD) +#include <sched.h> +#endif + +#define curl_simple_lock atomic_int +#define CURL_SIMPLE_LOCK_INIT 0 + +/* a clang-thing */ +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +#ifndef __INTEL_COMPILER +/* The Intel compiler tries to look like GCC *and* clang *and* lies in its + __has_builtin() function, so override it. */ + +/* if GCC on i386/x86_64 or if the built-in is present */ +#if ( (defined(__GNUC__) && !defined(__clang__)) && \ + (defined(__i386__) || defined(__x86_64__))) || \ + __has_builtin(__builtin_ia32_pause) +#define HAVE_BUILTIN_IA32_PAUSE +#endif + +#endif + +static inline void curl_simple_lock_lock(curl_simple_lock *lock) +{ + for(;;) { + if(!atomic_exchange_explicit(lock, true, memory_order_acquire)) + break; + /* Reduce cache coherency traffic */ + while(atomic_load_explicit(lock, memory_order_relaxed)) { + /* Reduce load (not mandatory) */ +#ifdef HAVE_BUILTIN_IA32_PAUSE + __builtin_ia32_pause(); +#elif defined(__aarch64__) + __asm__ volatile("yield" ::: "memory"); +#elif defined(HAVE_SCHED_YIELD) + sched_yield(); +#endif + } + } +} + +static inline void curl_simple_lock_unlock(curl_simple_lock *lock) +{ + atomic_store_explicit(lock, false, memory_order_release); +} + +#elif defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) + +#include <pthread.h> + +#define curl_simple_lock pthread_mutex_t +#define CURL_SIMPLE_LOCK_INIT PTHREAD_MUTEX_INITIALIZER +#define curl_simple_lock_lock(m) pthread_mutex_lock(m) +#define curl_simple_lock_unlock(m) pthread_mutex_unlock(m) + +#else + +#undef GLOBAL_INIT_IS_THREADSAFE + +#endif + +#endif /* HEADER_CURL_EASY_LOCK_H */ diff --git a/Utilities/cmcurl/lib/easygetopt.c b/Utilities/cmcurl/lib/easygetopt.c new file mode 100644 index 0000000..2b8a521 --- /dev/null +++ b/Utilities/cmcurl/lib/easygetopt.c @@ -0,0 +1,98 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ | | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * ___|___/|_| ______| + * + * Copyright (C) Daniel Stenberg, <daniel.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "strcase.h" +#include "easyoptions.h" + +#ifndef CURL_DISABLE_GETOPTIONS + +/* Lookups easy options at runtime */ +static struct curl_easyoption *lookup(const char *name, CURLoption id) +{ + DEBUGASSERT(name || id); + DEBUGASSERT(!Curl_easyopts_check()); + if(name || id) { + struct curl_easyoption *o = &Curl_easyopts[0]; + do { + if(name) { + if(strcasecompare(o->name, name)) + return o; + } + else { + if((o->id == id) && !(o->flags & CURLOT_FLAG_ALIAS)) + /* don't match alias options */ + return o; + } + o++; + } while(o->name); + } + return NULL; +} + +const struct curl_easyoption *curl_easy_option_by_name(const char *name) +{ + /* when name is used, the id argument is ignored */ + return lookup(name, CURLOPT_LASTENTRY); +} + +const struct curl_easyoption *curl_easy_option_by_id(CURLoption id) +{ + return lookup(NULL, id); +} + +/* Iterates over available options */ +const struct curl_easyoption * +curl_easy_option_next(const struct curl_easyoption *prev) +{ + if(prev && prev->name) { + prev++; + if(prev->name) + return prev; + } + else if(!prev) + return &Curl_easyopts[0]; + return NULL; +} + +#else +const struct curl_easyoption *curl_easy_option_by_name(const char *name) +{ + (void)name; + return NULL; +} + +const struct curl_easyoption *curl_easy_option_by_id (CURLoption id) +{ + (void)id; + return NULL; +} + +const struct curl_easyoption * +curl_easy_option_next(const struct curl_easyoption *prev) +{ + (void)prev; + return NULL; +} +#endif diff --git a/Utilities/cmcurl/lib/easyif.h b/Utilities/cmcurl/lib/easyif.h new file mode 100644 index 0000000..6448952 --- /dev/null +++ b/Utilities/cmcurl/lib/easyif.h @@ -0,0 +1,41 @@ +#ifndef HEADER_CURL_EASYIF_H +#define HEADER_CURL_EASYIF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Prototypes for library-wide functions provided by easy.c + */ +CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, + size_t buflen, ssize_t *n); + +#ifdef USE_WEBSOCKETS +CURLcode Curl_connect_only_attach(struct Curl_easy *data); +#endif + +#ifdef CURLDEBUG +CURL_EXTERN CURLcode curl_easy_perform_ev(struct Curl_easy *easy); +#endif + +#endif /* HEADER_CURL_EASYIF_H */ diff --git a/Utilities/cmcurl/lib/easyoptions.c b/Utilities/cmcurl/lib/easyoptions.c new file mode 100644 index 0000000..e69c658 --- /dev/null +++ b/Utilities/cmcurl/lib/easyoptions.c @@ -0,0 +1,378 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* This source code is generated by optiontable.pl - DO NOT EDIT BY HAND */ + +#include "curl_setup.h" +#include "easyoptions.h" + +/* all easy setopt options listed in alphabetical order */ +struct curl_easyoption Curl_easyopts[] = { + {"ABSTRACT_UNIX_SOCKET", CURLOPT_ABSTRACT_UNIX_SOCKET, CURLOT_STRING, 0}, + {"ACCEPTTIMEOUT_MS", CURLOPT_ACCEPTTIMEOUT_MS, CURLOT_LONG, 0}, + {"ACCEPT_ENCODING", CURLOPT_ACCEPT_ENCODING, CURLOT_STRING, 0}, + {"ADDRESS_SCOPE", CURLOPT_ADDRESS_SCOPE, CURLOT_LONG, 0}, + {"ALTSVC", CURLOPT_ALTSVC, CURLOT_STRING, 0}, + {"ALTSVC_CTRL", CURLOPT_ALTSVC_CTRL, CURLOT_LONG, 0}, + {"APPEND", CURLOPT_APPEND, CURLOT_LONG, 0}, + {"AUTOREFERER", CURLOPT_AUTOREFERER, CURLOT_LONG, 0}, + {"AWS_SIGV4", CURLOPT_AWS_SIGV4, CURLOT_STRING, 0}, + {"BUFFERSIZE", CURLOPT_BUFFERSIZE, CURLOT_LONG, 0}, + {"CAINFO", CURLOPT_CAINFO, CURLOT_STRING, 0}, + {"CAINFO_BLOB", CURLOPT_CAINFO_BLOB, CURLOT_BLOB, 0}, + {"CAPATH", CURLOPT_CAPATH, CURLOT_STRING, 0}, + {"CA_CACHE_TIMEOUT", CURLOPT_CA_CACHE_TIMEOUT, CURLOT_LONG, 0}, + {"CERTINFO", CURLOPT_CERTINFO, CURLOT_LONG, 0}, + {"CHUNK_BGN_FUNCTION", CURLOPT_CHUNK_BGN_FUNCTION, CURLOT_FUNCTION, 0}, + {"CHUNK_DATA", CURLOPT_CHUNK_DATA, CURLOT_CBPTR, 0}, + {"CHUNK_END_FUNCTION", CURLOPT_CHUNK_END_FUNCTION, CURLOT_FUNCTION, 0}, + {"CLOSESOCKETDATA", CURLOPT_CLOSESOCKETDATA, CURLOT_CBPTR, 0}, + {"CLOSESOCKETFUNCTION", CURLOPT_CLOSESOCKETFUNCTION, CURLOT_FUNCTION, 0}, + {"CONNECTTIMEOUT", CURLOPT_CONNECTTIMEOUT, CURLOT_LONG, 0}, + {"CONNECTTIMEOUT_MS", CURLOPT_CONNECTTIMEOUT_MS, CURLOT_LONG, 0}, + {"CONNECT_ONLY", CURLOPT_CONNECT_ONLY, CURLOT_LONG, 0}, + {"CONNECT_TO", CURLOPT_CONNECT_TO, CURLOT_SLIST, 0}, + {"CONV_FROM_NETWORK_FUNCTION", CURLOPT_CONV_FROM_NETWORK_FUNCTION, + CURLOT_FUNCTION, 0}, + {"CONV_FROM_UTF8_FUNCTION", CURLOPT_CONV_FROM_UTF8_FUNCTION, + CURLOT_FUNCTION, 0}, + {"CONV_TO_NETWORK_FUNCTION", CURLOPT_CONV_TO_NETWORK_FUNCTION, + CURLOT_FUNCTION, 0}, + {"COOKIE", CURLOPT_COOKIE, CURLOT_STRING, 0}, + {"COOKIEFILE", CURLOPT_COOKIEFILE, CURLOT_STRING, 0}, + {"COOKIEJAR", CURLOPT_COOKIEJAR, CURLOT_STRING, 0}, + {"COOKIELIST", CURLOPT_COOKIELIST, CURLOT_STRING, 0}, + {"COOKIESESSION", CURLOPT_COOKIESESSION, CURLOT_LONG, 0}, + {"COPYPOSTFIELDS", CURLOPT_COPYPOSTFIELDS, CURLOT_OBJECT, 0}, + {"CRLF", CURLOPT_CRLF, CURLOT_LONG, 0}, + {"CRLFILE", CURLOPT_CRLFILE, CURLOT_STRING, 0}, + {"CURLU", CURLOPT_CURLU, CURLOT_OBJECT, 0}, + {"CUSTOMREQUEST", CURLOPT_CUSTOMREQUEST, CURLOT_STRING, 0}, + {"DEBUGDATA", CURLOPT_DEBUGDATA, CURLOT_CBPTR, 0}, + {"DEBUGFUNCTION", CURLOPT_DEBUGFUNCTION, CURLOT_FUNCTION, 0}, + {"DEFAULT_PROTOCOL", CURLOPT_DEFAULT_PROTOCOL, CURLOT_STRING, 0}, + {"DIRLISTONLY", CURLOPT_DIRLISTONLY, CURLOT_LONG, 0}, + {"DISALLOW_USERNAME_IN_URL", CURLOPT_DISALLOW_USERNAME_IN_URL, + CURLOT_LONG, 0}, + {"DNS_CACHE_TIMEOUT", CURLOPT_DNS_CACHE_TIMEOUT, CURLOT_LONG, 0}, + {"DNS_INTERFACE", CURLOPT_DNS_INTERFACE, CURLOT_STRING, 0}, + {"DNS_LOCAL_IP4", CURLOPT_DNS_LOCAL_IP4, CURLOT_STRING, 0}, + {"DNS_LOCAL_IP6", CURLOPT_DNS_LOCAL_IP6, CURLOT_STRING, 0}, + {"DNS_SERVERS", CURLOPT_DNS_SERVERS, CURLOT_STRING, 0}, + {"DNS_SHUFFLE_ADDRESSES", CURLOPT_DNS_SHUFFLE_ADDRESSES, CURLOT_LONG, 0}, + {"DNS_USE_GLOBAL_CACHE", CURLOPT_DNS_USE_GLOBAL_CACHE, CURLOT_LONG, 0}, + {"DOH_SSL_VERIFYHOST", CURLOPT_DOH_SSL_VERIFYHOST, CURLOT_LONG, 0}, + {"DOH_SSL_VERIFYPEER", CURLOPT_DOH_SSL_VERIFYPEER, CURLOT_LONG, 0}, + {"DOH_SSL_VERIFYSTATUS", CURLOPT_DOH_SSL_VERIFYSTATUS, CURLOT_LONG, 0}, + {"DOH_URL", CURLOPT_DOH_URL, CURLOT_STRING, 0}, + {"EGDSOCKET", CURLOPT_EGDSOCKET, CURLOT_STRING, 0}, + {"ENCODING", CURLOPT_ACCEPT_ENCODING, CURLOT_STRING, CURLOT_FLAG_ALIAS}, + {"ERRORBUFFER", CURLOPT_ERRORBUFFER, CURLOT_OBJECT, 0}, + {"EXPECT_100_TIMEOUT_MS", CURLOPT_EXPECT_100_TIMEOUT_MS, CURLOT_LONG, 0}, + {"FAILONERROR", CURLOPT_FAILONERROR, CURLOT_LONG, 0}, + {"FILE", CURLOPT_WRITEDATA, CURLOT_CBPTR, CURLOT_FLAG_ALIAS}, + {"FILETIME", CURLOPT_FILETIME, CURLOT_LONG, 0}, + {"FNMATCH_DATA", CURLOPT_FNMATCH_DATA, CURLOT_CBPTR, 0}, + {"FNMATCH_FUNCTION", CURLOPT_FNMATCH_FUNCTION, CURLOT_FUNCTION, 0}, + {"FOLLOWLOCATION", CURLOPT_FOLLOWLOCATION, CURLOT_LONG, 0}, + {"FORBID_REUSE", CURLOPT_FORBID_REUSE, CURLOT_LONG, 0}, + {"FRESH_CONNECT", CURLOPT_FRESH_CONNECT, CURLOT_LONG, 0}, + {"FTPAPPEND", CURLOPT_APPEND, CURLOT_LONG, CURLOT_FLAG_ALIAS}, + {"FTPLISTONLY", CURLOPT_DIRLISTONLY, CURLOT_LONG, CURLOT_FLAG_ALIAS}, + {"FTPPORT", CURLOPT_FTPPORT, CURLOT_STRING, 0}, + {"FTPSSLAUTH", CURLOPT_FTPSSLAUTH, CURLOT_VALUES, 0}, + {"FTP_ACCOUNT", CURLOPT_FTP_ACCOUNT, CURLOT_STRING, 0}, + {"FTP_ALTERNATIVE_TO_USER", CURLOPT_FTP_ALTERNATIVE_TO_USER, + CURLOT_STRING, 0}, + {"FTP_CREATE_MISSING_DIRS", CURLOPT_FTP_CREATE_MISSING_DIRS, + CURLOT_LONG, 0}, + {"FTP_FILEMETHOD", CURLOPT_FTP_FILEMETHOD, CURLOT_VALUES, 0}, + {"FTP_RESPONSE_TIMEOUT", CURLOPT_SERVER_RESPONSE_TIMEOUT, + CURLOT_LONG, CURLOT_FLAG_ALIAS}, + {"FTP_SKIP_PASV_IP", CURLOPT_FTP_SKIP_PASV_IP, CURLOT_LONG, 0}, + {"FTP_SSL", CURLOPT_USE_SSL, CURLOT_VALUES, CURLOT_FLAG_ALIAS}, + {"FTP_SSL_CCC", CURLOPT_FTP_SSL_CCC, CURLOT_LONG, 0}, + {"FTP_USE_EPRT", CURLOPT_FTP_USE_EPRT, CURLOT_LONG, 0}, + {"FTP_USE_EPSV", CURLOPT_FTP_USE_EPSV, CURLOT_LONG, 0}, + {"FTP_USE_PRET", CURLOPT_FTP_USE_PRET, CURLOT_LONG, 0}, + {"GSSAPI_DELEGATION", CURLOPT_GSSAPI_DELEGATION, CURLOT_VALUES, 0}, + {"HAPPY_EYEBALLS_TIMEOUT_MS", CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS, + CURLOT_LONG, 0}, + {"HAPROXYPROTOCOL", CURLOPT_HAPROXYPROTOCOL, CURLOT_LONG, 0}, + {"HAPROXY_CLIENT_IP", CURLOPT_HAPROXY_CLIENT_IP, CURLOT_STRING, 0}, + {"HEADER", CURLOPT_HEADER, CURLOT_LONG, 0}, + {"HEADERDATA", CURLOPT_HEADERDATA, CURLOT_CBPTR, 0}, + {"HEADERFUNCTION", CURLOPT_HEADERFUNCTION, CURLOT_FUNCTION, 0}, + {"HEADEROPT", CURLOPT_HEADEROPT, CURLOT_VALUES, 0}, + {"HSTS", CURLOPT_HSTS, CURLOT_STRING, 0}, + {"HSTSREADDATA", CURLOPT_HSTSREADDATA, CURLOT_CBPTR, 0}, + {"HSTSREADFUNCTION", CURLOPT_HSTSREADFUNCTION, CURLOT_FUNCTION, 0}, + {"HSTSWRITEDATA", CURLOPT_HSTSWRITEDATA, CURLOT_CBPTR, 0}, + {"HSTSWRITEFUNCTION", CURLOPT_HSTSWRITEFUNCTION, CURLOT_FUNCTION, 0}, + {"HSTS_CTRL", CURLOPT_HSTS_CTRL, CURLOT_LONG, 0}, + {"HTTP09_ALLOWED", CURLOPT_HTTP09_ALLOWED, CURLOT_LONG, 0}, + {"HTTP200ALIASES", CURLOPT_HTTP200ALIASES, CURLOT_SLIST, 0}, + {"HTTPAUTH", CURLOPT_HTTPAUTH, CURLOT_VALUES, 0}, + {"HTTPGET", CURLOPT_HTTPGET, CURLOT_LONG, 0}, + {"HTTPHEADER", CURLOPT_HTTPHEADER, CURLOT_SLIST, 0}, + {"HTTPPOST", CURLOPT_HTTPPOST, CURLOT_OBJECT, 0}, + {"HTTPPROXYTUNNEL", CURLOPT_HTTPPROXYTUNNEL, CURLOT_LONG, 0}, + {"HTTP_CONTENT_DECODING", CURLOPT_HTTP_CONTENT_DECODING, CURLOT_LONG, 0}, + {"HTTP_TRANSFER_DECODING", CURLOPT_HTTP_TRANSFER_DECODING, CURLOT_LONG, 0}, + {"HTTP_VERSION", CURLOPT_HTTP_VERSION, CURLOT_VALUES, 0}, + {"IGNORE_CONTENT_LENGTH", CURLOPT_IGNORE_CONTENT_LENGTH, CURLOT_LONG, 0}, + {"INFILE", CURLOPT_READDATA, CURLOT_CBPTR, CURLOT_FLAG_ALIAS}, + {"INFILESIZE", CURLOPT_INFILESIZE, CURLOT_LONG, 0}, + {"INFILESIZE_LARGE", CURLOPT_INFILESIZE_LARGE, CURLOT_OFF_T, 0}, + {"INTERFACE", CURLOPT_INTERFACE, CURLOT_STRING, 0}, + {"INTERLEAVEDATA", CURLOPT_INTERLEAVEDATA, CURLOT_CBPTR, 0}, + {"INTERLEAVEFUNCTION", CURLOPT_INTERLEAVEFUNCTION, CURLOT_FUNCTION, 0}, + {"IOCTLDATA", CURLOPT_IOCTLDATA, CURLOT_CBPTR, 0}, + {"IOCTLFUNCTION", CURLOPT_IOCTLFUNCTION, CURLOT_FUNCTION, 0}, + {"IPRESOLVE", CURLOPT_IPRESOLVE, CURLOT_VALUES, 0}, + {"ISSUERCERT", CURLOPT_ISSUERCERT, CURLOT_STRING, 0}, + {"ISSUERCERT_BLOB", CURLOPT_ISSUERCERT_BLOB, CURLOT_BLOB, 0}, + {"KEEP_SENDING_ON_ERROR", CURLOPT_KEEP_SENDING_ON_ERROR, CURLOT_LONG, 0}, + {"KEYPASSWD", CURLOPT_KEYPASSWD, CURLOT_STRING, 0}, + {"KRB4LEVEL", CURLOPT_KRBLEVEL, CURLOT_STRING, CURLOT_FLAG_ALIAS}, + {"KRBLEVEL", CURLOPT_KRBLEVEL, CURLOT_STRING, 0}, + {"LOCALPORT", CURLOPT_LOCALPORT, CURLOT_LONG, 0}, + {"LOCALPORTRANGE", CURLOPT_LOCALPORTRANGE, CURLOT_LONG, 0}, + {"LOGIN_OPTIONS", CURLOPT_LOGIN_OPTIONS, CURLOT_STRING, 0}, + {"LOW_SPEED_LIMIT", CURLOPT_LOW_SPEED_LIMIT, CURLOT_LONG, 0}, + {"LOW_SPEED_TIME", CURLOPT_LOW_SPEED_TIME, CURLOT_LONG, 0}, + {"MAIL_AUTH", CURLOPT_MAIL_AUTH, CURLOT_STRING, 0}, + {"MAIL_FROM", CURLOPT_MAIL_FROM, CURLOT_STRING, 0}, + {"MAIL_RCPT", CURLOPT_MAIL_RCPT, CURLOT_SLIST, 0}, + {"MAIL_RCPT_ALLLOWFAILS", CURLOPT_MAIL_RCPT_ALLOWFAILS, + CURLOT_LONG, CURLOT_FLAG_ALIAS}, + {"MAIL_RCPT_ALLOWFAILS", CURLOPT_MAIL_RCPT_ALLOWFAILS, CURLOT_LONG, 0}, + {"MAXAGE_CONN", CURLOPT_MAXAGE_CONN, CURLOT_LONG, 0}, + {"MAXCONNECTS", CURLOPT_MAXCONNECTS, CURLOT_LONG, 0}, + {"MAXFILESIZE", CURLOPT_MAXFILESIZE, CURLOT_LONG, 0}, + {"MAXFILESIZE_LARGE", CURLOPT_MAXFILESIZE_LARGE, CURLOT_OFF_T, 0}, + {"MAXLIFETIME_CONN", CURLOPT_MAXLIFETIME_CONN, CURLOT_LONG, 0}, + {"MAXREDIRS", CURLOPT_MAXREDIRS, CURLOT_LONG, 0}, + {"MAX_RECV_SPEED_LARGE", CURLOPT_MAX_RECV_SPEED_LARGE, CURLOT_OFF_T, 0}, + {"MAX_SEND_SPEED_LARGE", CURLOPT_MAX_SEND_SPEED_LARGE, CURLOT_OFF_T, 0}, + {"MIMEPOST", CURLOPT_MIMEPOST, CURLOT_OBJECT, 0}, + {"MIME_OPTIONS", CURLOPT_MIME_OPTIONS, CURLOT_LONG, 0}, + {"NETRC", CURLOPT_NETRC, CURLOT_VALUES, 0}, + {"NETRC_FILE", CURLOPT_NETRC_FILE, CURLOT_STRING, 0}, + {"NEW_DIRECTORY_PERMS", CURLOPT_NEW_DIRECTORY_PERMS, CURLOT_LONG, 0}, + {"NEW_FILE_PERMS", CURLOPT_NEW_FILE_PERMS, CURLOT_LONG, 0}, + {"NOBODY", CURLOPT_NOBODY, CURLOT_LONG, 0}, + {"NOPROGRESS", CURLOPT_NOPROGRESS, CURLOT_LONG, 0}, + {"NOPROXY", CURLOPT_NOPROXY, CURLOT_STRING, 0}, + {"NOSIGNAL", CURLOPT_NOSIGNAL, CURLOT_LONG, 0}, + {"OPENSOCKETDATA", CURLOPT_OPENSOCKETDATA, CURLOT_CBPTR, 0}, + {"OPENSOCKETFUNCTION", CURLOPT_OPENSOCKETFUNCTION, CURLOT_FUNCTION, 0}, + {"PASSWORD", CURLOPT_PASSWORD, CURLOT_STRING, 0}, + {"PATH_AS_IS", CURLOPT_PATH_AS_IS, CURLOT_LONG, 0}, + {"PINNEDPUBLICKEY", CURLOPT_PINNEDPUBLICKEY, CURLOT_STRING, 0}, + {"PIPEWAIT", CURLOPT_PIPEWAIT, CURLOT_LONG, 0}, + {"PORT", CURLOPT_PORT, CURLOT_LONG, 0}, + {"POST", CURLOPT_POST, CURLOT_LONG, 0}, + {"POST301", CURLOPT_POSTREDIR, CURLOT_VALUES, CURLOT_FLAG_ALIAS}, + {"POSTFIELDS", CURLOPT_POSTFIELDS, CURLOT_OBJECT, 0}, + {"POSTFIELDSIZE", CURLOPT_POSTFIELDSIZE, CURLOT_LONG, 0}, + {"POSTFIELDSIZE_LARGE", CURLOPT_POSTFIELDSIZE_LARGE, CURLOT_OFF_T, 0}, + {"POSTQUOTE", CURLOPT_POSTQUOTE, CURLOT_SLIST, 0}, + {"POSTREDIR", CURLOPT_POSTREDIR, CURLOT_VALUES, 0}, + {"PREQUOTE", CURLOPT_PREQUOTE, CURLOT_SLIST, 0}, + {"PREREQDATA", CURLOPT_PREREQDATA, CURLOT_CBPTR, 0}, + {"PREREQFUNCTION", CURLOPT_PREREQFUNCTION, CURLOT_FUNCTION, 0}, + {"PRE_PROXY", CURLOPT_PRE_PROXY, CURLOT_STRING, 0}, + {"PRIVATE", CURLOPT_PRIVATE, CURLOT_OBJECT, 0}, + {"PROGRESSDATA", CURLOPT_XFERINFODATA, CURLOT_CBPTR, CURLOT_FLAG_ALIAS}, + {"PROGRESSFUNCTION", CURLOPT_PROGRESSFUNCTION, CURLOT_FUNCTION, 0}, + {"PROTOCOLS", CURLOPT_PROTOCOLS, CURLOT_LONG, 0}, + {"PROTOCOLS_STR", CURLOPT_PROTOCOLS_STR, CURLOT_STRING, 0}, + {"PROXY", CURLOPT_PROXY, CURLOT_STRING, 0}, + {"PROXYAUTH", CURLOPT_PROXYAUTH, CURLOT_VALUES, 0}, + {"PROXYHEADER", CURLOPT_PROXYHEADER, CURLOT_SLIST, 0}, + {"PROXYPASSWORD", CURLOPT_PROXYPASSWORD, CURLOT_STRING, 0}, + {"PROXYPORT", CURLOPT_PROXYPORT, CURLOT_LONG, 0}, + {"PROXYTYPE", CURLOPT_PROXYTYPE, CURLOT_VALUES, 0}, + {"PROXYUSERNAME", CURLOPT_PROXYUSERNAME, CURLOT_STRING, 0}, + {"PROXYUSERPWD", CURLOPT_PROXYUSERPWD, CURLOT_STRING, 0}, + {"PROXY_CAINFO", CURLOPT_PROXY_CAINFO, CURLOT_STRING, 0}, + {"PROXY_CAINFO_BLOB", CURLOPT_PROXY_CAINFO_BLOB, CURLOT_BLOB, 0}, + {"PROXY_CAPATH", CURLOPT_PROXY_CAPATH, CURLOT_STRING, 0}, + {"PROXY_CRLFILE", CURLOPT_PROXY_CRLFILE, CURLOT_STRING, 0}, + {"PROXY_ISSUERCERT", CURLOPT_PROXY_ISSUERCERT, CURLOT_STRING, 0}, + {"PROXY_ISSUERCERT_BLOB", CURLOPT_PROXY_ISSUERCERT_BLOB, CURLOT_BLOB, 0}, + {"PROXY_KEYPASSWD", CURLOPT_PROXY_KEYPASSWD, CURLOT_STRING, 0}, + {"PROXY_PINNEDPUBLICKEY", CURLOPT_PROXY_PINNEDPUBLICKEY, CURLOT_STRING, 0}, + {"PROXY_SERVICE_NAME", CURLOPT_PROXY_SERVICE_NAME, CURLOT_STRING, 0}, + {"PROXY_SSLCERT", CURLOPT_PROXY_SSLCERT, CURLOT_STRING, 0}, + {"PROXY_SSLCERTTYPE", CURLOPT_PROXY_SSLCERTTYPE, CURLOT_STRING, 0}, + {"PROXY_SSLCERT_BLOB", CURLOPT_PROXY_SSLCERT_BLOB, CURLOT_BLOB, 0}, + {"PROXY_SSLKEY", CURLOPT_PROXY_SSLKEY, CURLOT_STRING, 0}, + {"PROXY_SSLKEYTYPE", CURLOPT_PROXY_SSLKEYTYPE, CURLOT_STRING, 0}, + {"PROXY_SSLKEY_BLOB", CURLOPT_PROXY_SSLKEY_BLOB, CURLOT_BLOB, 0}, + {"PROXY_SSLVERSION", CURLOPT_PROXY_SSLVERSION, CURLOT_VALUES, 0}, + {"PROXY_SSL_CIPHER_LIST", CURLOPT_PROXY_SSL_CIPHER_LIST, CURLOT_STRING, 0}, + {"PROXY_SSL_OPTIONS", CURLOPT_PROXY_SSL_OPTIONS, CURLOT_LONG, 0}, + {"PROXY_SSL_VERIFYHOST", CURLOPT_PROXY_SSL_VERIFYHOST, CURLOT_LONG, 0}, + {"PROXY_SSL_VERIFYPEER", CURLOPT_PROXY_SSL_VERIFYPEER, CURLOT_LONG, 0}, + {"PROXY_TLS13_CIPHERS", CURLOPT_PROXY_TLS13_CIPHERS, CURLOT_STRING, 0}, + {"PROXY_TLSAUTH_PASSWORD", CURLOPT_PROXY_TLSAUTH_PASSWORD, + CURLOT_STRING, 0}, + {"PROXY_TLSAUTH_TYPE", CURLOPT_PROXY_TLSAUTH_TYPE, CURLOT_STRING, 0}, + {"PROXY_TLSAUTH_USERNAME", CURLOPT_PROXY_TLSAUTH_USERNAME, + CURLOT_STRING, 0}, + {"PROXY_TRANSFER_MODE", CURLOPT_PROXY_TRANSFER_MODE, CURLOT_LONG, 0}, + {"PUT", CURLOPT_PUT, CURLOT_LONG, 0}, + {"QUICK_EXIT", CURLOPT_QUICK_EXIT, CURLOT_LONG, 0}, + {"QUOTE", CURLOPT_QUOTE, CURLOT_SLIST, 0}, + {"RANDOM_FILE", CURLOPT_RANDOM_FILE, CURLOT_STRING, 0}, + {"RANGE", CURLOPT_RANGE, CURLOT_STRING, 0}, + {"READDATA", CURLOPT_READDATA, CURLOT_CBPTR, 0}, + {"READFUNCTION", CURLOPT_READFUNCTION, CURLOT_FUNCTION, 0}, + {"REDIR_PROTOCOLS", CURLOPT_REDIR_PROTOCOLS, CURLOT_LONG, 0}, + {"REDIR_PROTOCOLS_STR", CURLOPT_REDIR_PROTOCOLS_STR, CURLOT_STRING, 0}, + {"REFERER", CURLOPT_REFERER, CURLOT_STRING, 0}, + {"REQUEST_TARGET", CURLOPT_REQUEST_TARGET, CURLOT_STRING, 0}, + {"RESOLVE", CURLOPT_RESOLVE, CURLOT_SLIST, 0}, + {"RESOLVER_START_DATA", CURLOPT_RESOLVER_START_DATA, CURLOT_CBPTR, 0}, + {"RESOLVER_START_FUNCTION", CURLOPT_RESOLVER_START_FUNCTION, + CURLOT_FUNCTION, 0}, + {"RESUME_FROM", CURLOPT_RESUME_FROM, CURLOT_LONG, 0}, + {"RESUME_FROM_LARGE", CURLOPT_RESUME_FROM_LARGE, CURLOT_OFF_T, 0}, + {"RTSPHEADER", CURLOPT_HTTPHEADER, CURLOT_SLIST, CURLOT_FLAG_ALIAS}, + {"RTSP_CLIENT_CSEQ", CURLOPT_RTSP_CLIENT_CSEQ, CURLOT_LONG, 0}, + {"RTSP_REQUEST", CURLOPT_RTSP_REQUEST, CURLOT_VALUES, 0}, + {"RTSP_SERVER_CSEQ", CURLOPT_RTSP_SERVER_CSEQ, CURLOT_LONG, 0}, + {"RTSP_SESSION_ID", CURLOPT_RTSP_SESSION_ID, CURLOT_STRING, 0}, + {"RTSP_STREAM_URI", CURLOPT_RTSP_STREAM_URI, CURLOT_STRING, 0}, + {"RTSP_TRANSPORT", CURLOPT_RTSP_TRANSPORT, CURLOT_STRING, 0}, + {"SASL_AUTHZID", CURLOPT_SASL_AUTHZID, CURLOT_STRING, 0}, + {"SASL_IR", CURLOPT_SASL_IR, CURLOT_LONG, 0}, + {"SEEKDATA", CURLOPT_SEEKDATA, CURLOT_CBPTR, 0}, + {"SEEKFUNCTION", CURLOPT_SEEKFUNCTION, CURLOT_FUNCTION, 0}, + {"SERVER_RESPONSE_TIMEOUT", CURLOPT_SERVER_RESPONSE_TIMEOUT, + CURLOT_LONG, 0}, + {"SERVICE_NAME", CURLOPT_SERVICE_NAME, CURLOT_STRING, 0}, + {"SHARE", CURLOPT_SHARE, CURLOT_OBJECT, 0}, + {"SOCKOPTDATA", CURLOPT_SOCKOPTDATA, CURLOT_CBPTR, 0}, + {"SOCKOPTFUNCTION", CURLOPT_SOCKOPTFUNCTION, CURLOT_FUNCTION, 0}, + {"SOCKS5_AUTH", CURLOPT_SOCKS5_AUTH, CURLOT_LONG, 0}, + {"SOCKS5_GSSAPI_NEC", CURLOPT_SOCKS5_GSSAPI_NEC, CURLOT_LONG, 0}, + {"SOCKS5_GSSAPI_SERVICE", CURLOPT_SOCKS5_GSSAPI_SERVICE, CURLOT_STRING, 0}, + {"SSH_AUTH_TYPES", CURLOPT_SSH_AUTH_TYPES, CURLOT_VALUES, 0}, + {"SSH_COMPRESSION", CURLOPT_SSH_COMPRESSION, CURLOT_LONG, 0}, + {"SSH_HOSTKEYDATA", CURLOPT_SSH_HOSTKEYDATA, CURLOT_CBPTR, 0}, + {"SSH_HOSTKEYFUNCTION", CURLOPT_SSH_HOSTKEYFUNCTION, CURLOT_FUNCTION, 0}, + {"SSH_HOST_PUBLIC_KEY_MD5", CURLOPT_SSH_HOST_PUBLIC_KEY_MD5, + CURLOT_STRING, 0}, + {"SSH_HOST_PUBLIC_KEY_SHA256", CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, + CURLOT_STRING, 0}, + {"SSH_KEYDATA", CURLOPT_SSH_KEYDATA, CURLOT_CBPTR, 0}, + {"SSH_KEYFUNCTION", CURLOPT_SSH_KEYFUNCTION, CURLOT_FUNCTION, 0}, + {"SSH_KNOWNHOSTS", CURLOPT_SSH_KNOWNHOSTS, CURLOT_STRING, 0}, + {"SSH_PRIVATE_KEYFILE", CURLOPT_SSH_PRIVATE_KEYFILE, CURLOT_STRING, 0}, + {"SSH_PUBLIC_KEYFILE", CURLOPT_SSH_PUBLIC_KEYFILE, CURLOT_STRING, 0}, + {"SSLCERT", CURLOPT_SSLCERT, CURLOT_STRING, 0}, + {"SSLCERTPASSWD", CURLOPT_KEYPASSWD, CURLOT_STRING, CURLOT_FLAG_ALIAS}, + {"SSLCERTTYPE", CURLOPT_SSLCERTTYPE, CURLOT_STRING, 0}, + {"SSLCERT_BLOB", CURLOPT_SSLCERT_BLOB, CURLOT_BLOB, 0}, + {"SSLENGINE", CURLOPT_SSLENGINE, CURLOT_STRING, 0}, + {"SSLENGINE_DEFAULT", CURLOPT_SSLENGINE_DEFAULT, CURLOT_LONG, 0}, + {"SSLKEY", CURLOPT_SSLKEY, CURLOT_STRING, 0}, + {"SSLKEYPASSWD", CURLOPT_KEYPASSWD, CURLOT_STRING, CURLOT_FLAG_ALIAS}, + {"SSLKEYTYPE", CURLOPT_SSLKEYTYPE, CURLOT_STRING, 0}, + {"SSLKEY_BLOB", CURLOPT_SSLKEY_BLOB, CURLOT_BLOB, 0}, + {"SSLVERSION", CURLOPT_SSLVERSION, CURLOT_VALUES, 0}, + {"SSL_CIPHER_LIST", CURLOPT_SSL_CIPHER_LIST, CURLOT_STRING, 0}, + {"SSL_CTX_DATA", CURLOPT_SSL_CTX_DATA, CURLOT_CBPTR, 0}, + {"SSL_CTX_FUNCTION", CURLOPT_SSL_CTX_FUNCTION, CURLOT_FUNCTION, 0}, + {"SSL_EC_CURVES", CURLOPT_SSL_EC_CURVES, CURLOT_STRING, 0}, + {"SSL_ENABLE_ALPN", CURLOPT_SSL_ENABLE_ALPN, CURLOT_LONG, 0}, + {"SSL_ENABLE_NPN", CURLOPT_SSL_ENABLE_NPN, CURLOT_LONG, 0}, + {"SSL_FALSESTART", CURLOPT_SSL_FALSESTART, CURLOT_LONG, 0}, + {"SSL_OPTIONS", CURLOPT_SSL_OPTIONS, CURLOT_VALUES, 0}, + {"SSL_SESSIONID_CACHE", CURLOPT_SSL_SESSIONID_CACHE, CURLOT_LONG, 0}, + {"SSL_VERIFYHOST", CURLOPT_SSL_VERIFYHOST, CURLOT_LONG, 0}, + {"SSL_VERIFYPEER", CURLOPT_SSL_VERIFYPEER, CURLOT_LONG, 0}, + {"SSL_VERIFYSTATUS", CURLOPT_SSL_VERIFYSTATUS, CURLOT_LONG, 0}, + {"STDERR", CURLOPT_STDERR, CURLOT_OBJECT, 0}, + {"STREAM_DEPENDS", CURLOPT_STREAM_DEPENDS, CURLOT_OBJECT, 0}, + {"STREAM_DEPENDS_E", CURLOPT_STREAM_DEPENDS_E, CURLOT_OBJECT, 0}, + {"STREAM_WEIGHT", CURLOPT_STREAM_WEIGHT, CURLOT_LONG, 0}, + {"SUPPRESS_CONNECT_HEADERS", CURLOPT_SUPPRESS_CONNECT_HEADERS, + CURLOT_LONG, 0}, + {"TCP_FASTOPEN", CURLOPT_TCP_FASTOPEN, CURLOT_LONG, 0}, + {"TCP_KEEPALIVE", CURLOPT_TCP_KEEPALIVE, CURLOT_LONG, 0}, + {"TCP_KEEPIDLE", CURLOPT_TCP_KEEPIDLE, CURLOT_LONG, 0}, + {"TCP_KEEPINTVL", CURLOPT_TCP_KEEPINTVL, CURLOT_LONG, 0}, + {"TCP_NODELAY", CURLOPT_TCP_NODELAY, CURLOT_LONG, 0}, + {"TELNETOPTIONS", CURLOPT_TELNETOPTIONS, CURLOT_SLIST, 0}, + {"TFTP_BLKSIZE", CURLOPT_TFTP_BLKSIZE, CURLOT_LONG, 0}, + {"TFTP_NO_OPTIONS", CURLOPT_TFTP_NO_OPTIONS, CURLOT_LONG, 0}, + {"TIMECONDITION", CURLOPT_TIMECONDITION, CURLOT_VALUES, 0}, + {"TIMEOUT", CURLOPT_TIMEOUT, CURLOT_LONG, 0}, + {"TIMEOUT_MS", CURLOPT_TIMEOUT_MS, CURLOT_LONG, 0}, + {"TIMEVALUE", CURLOPT_TIMEVALUE, CURLOT_LONG, 0}, + {"TIMEVALUE_LARGE", CURLOPT_TIMEVALUE_LARGE, CURLOT_OFF_T, 0}, + {"TLS13_CIPHERS", CURLOPT_TLS13_CIPHERS, CURLOT_STRING, 0}, + {"TLSAUTH_PASSWORD", CURLOPT_TLSAUTH_PASSWORD, CURLOT_STRING, 0}, + {"TLSAUTH_TYPE", CURLOPT_TLSAUTH_TYPE, CURLOT_STRING, 0}, + {"TLSAUTH_USERNAME", CURLOPT_TLSAUTH_USERNAME, CURLOT_STRING, 0}, + {"TRAILERDATA", CURLOPT_TRAILERDATA, CURLOT_CBPTR, 0}, + {"TRAILERFUNCTION", CURLOPT_TRAILERFUNCTION, CURLOT_FUNCTION, 0}, + {"TRANSFERTEXT", CURLOPT_TRANSFERTEXT, CURLOT_LONG, 0}, + {"TRANSFER_ENCODING", CURLOPT_TRANSFER_ENCODING, CURLOT_LONG, 0}, + {"UNIX_SOCKET_PATH", CURLOPT_UNIX_SOCKET_PATH, CURLOT_STRING, 0}, + {"UNRESTRICTED_AUTH", CURLOPT_UNRESTRICTED_AUTH, CURLOT_LONG, 0}, + {"UPKEEP_INTERVAL_MS", CURLOPT_UPKEEP_INTERVAL_MS, CURLOT_LONG, 0}, + {"UPLOAD", CURLOPT_UPLOAD, CURLOT_LONG, 0}, + {"UPLOAD_BUFFERSIZE", CURLOPT_UPLOAD_BUFFERSIZE, CURLOT_LONG, 0}, + {"URL", CURLOPT_URL, CURLOT_STRING, 0}, + {"USERAGENT", CURLOPT_USERAGENT, CURLOT_STRING, 0}, + {"USERNAME", CURLOPT_USERNAME, CURLOT_STRING, 0}, + {"USERPWD", CURLOPT_USERPWD, CURLOT_STRING, 0}, + {"USE_SSL", CURLOPT_USE_SSL, CURLOT_VALUES, 0}, + {"VERBOSE", CURLOPT_VERBOSE, CURLOT_LONG, 0}, + {"WILDCARDMATCH", CURLOPT_WILDCARDMATCH, CURLOT_LONG, 0}, + {"WRITEDATA", CURLOPT_WRITEDATA, CURLOT_CBPTR, 0}, + {"WRITEFUNCTION", CURLOPT_WRITEFUNCTION, CURLOT_FUNCTION, 0}, + {"WRITEHEADER", CURLOPT_HEADERDATA, CURLOT_CBPTR, CURLOT_FLAG_ALIAS}, + {"WS_OPTIONS", CURLOPT_WS_OPTIONS, CURLOT_LONG, 0}, + {"XFERINFODATA", CURLOPT_XFERINFODATA, CURLOT_CBPTR, 0}, + {"XFERINFOFUNCTION", CURLOPT_XFERINFOFUNCTION, CURLOT_FUNCTION, 0}, + {"XOAUTH2_BEARER", CURLOPT_XOAUTH2_BEARER, CURLOT_STRING, 0}, + {NULL, CURLOPT_LASTENTRY, CURLOT_LONG, 0} /* end of table */ +}; + +#ifdef DEBUGBUILD +/* + * Curl_easyopts_check() is a debug-only function that returns non-zero + * if this source file is not in sync with the options listed in curl/curl.h + */ +int Curl_easyopts_check(void) +{ + return ((CURLOPT_LASTENTRY%10000) != (323 + 1)); +} +#endif diff --git a/Utilities/cmcurl/lib/easyoptions.h b/Utilities/cmcurl/lib/easyoptions.h new file mode 100644 index 0000000..24b4cd9 --- /dev/null +++ b/Utilities/cmcurl/lib/easyoptions.h @@ -0,0 +1,37 @@ +#ifndef HEADER_CURL_EASYOPTIONS_H +#define HEADER_CURL_EASYOPTIONS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* should probably go into the public header */ + +#include <curl/curl.h> + +/* generated table with all easy options */ +extern struct curl_easyoption Curl_easyopts[]; + +#ifdef DEBUGBUILD +int Curl_easyopts_check(void); +#endif +#endif diff --git a/Utilities/cmcurl/lib/escape.c b/Utilities/cmcurl/lib/escape.c new file mode 100644 index 0000000..5af00c3 --- /dev/null +++ b/Utilities/cmcurl/lib/escape.c @@ -0,0 +1,234 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* Escape and unescape URL encoding in strings. The functions return a new + * allocated string or NULL if an error occurred. */ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "urldata.h" +#include "warnless.h" +#include "escape.h" +#include "strdup.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* for ABI-compatibility with previous versions */ +char *curl_escape(const char *string, int inlength) +{ + return curl_easy_escape(NULL, string, inlength); +} + +/* for ABI-compatibility with previous versions */ +char *curl_unescape(const char *string, int length) +{ + return curl_easy_unescape(NULL, string, length, NULL); +} + +/* Escapes for URL the given unescaped string of given length. + * 'data' is ignored since 7.82.0. + */ +char *curl_easy_escape(struct Curl_easy *data, const char *string, + int inlength) +{ + size_t length; + struct dynbuf d; + (void)data; + + if(inlength < 0) + return NULL; + + Curl_dyn_init(&d, CURL_MAX_INPUT_LENGTH * 3); + + length = (inlength?(size_t)inlength:strlen(string)); + if(!length) + return strdup(""); + + while(length--) { + unsigned char in = *string++; /* treat the characters unsigned */ + + if(ISUNRESERVED(in)) { + /* append this */ + if(Curl_dyn_addn(&d, &in, 1)) + return NULL; + } + else { + /* encode it */ + const char hex[] = "0123456789ABCDEF"; + char out[3]={'%'}; + out[1] = hex[in>>4]; + out[2] = hex[in & 0xf]; + if(Curl_dyn_addn(&d, out, 3)) + return NULL; + } + } + + return Curl_dyn_ptr(&d); +} + +static const unsigned char hextable[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */ + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */ + 0, 10, 11, 12, 13, 14, 15 /* 0x60 - 0x66 */ +}; + +/* the input is a single hex digit */ +#define onehex2dec(x) hextable[x - '0'] + +/* + * Curl_urldecode() URL decodes the given string. + * + * Returns a pointer to a malloced string in *ostring with length given in + * *olen. If length == 0, the length is assumed to be strlen(string). + * + * ctrl options: + * - REJECT_NADA: accept everything + * - REJECT_CTRL: rejects control characters (byte codes lower than 32) in + * the data + * - REJECT_ZERO: rejects decoded zero bytes + * + * The values for the enum starts at 2, to make the assert detect legacy + * invokes that used TRUE/FALSE (0 and 1). + */ + +CURLcode Curl_urldecode(const char *string, size_t length, + char **ostring, size_t *olen, + enum urlreject ctrl) +{ + size_t alloc; + char *ns; + + DEBUGASSERT(string); + DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */ + + alloc = (length?length:strlen(string)); + ns = malloc(alloc + 1); + + if(!ns) + return CURLE_OUT_OF_MEMORY; + + /* store output string */ + *ostring = ns; + + while(alloc) { + unsigned char in = *string; + if(('%' == in) && (alloc > 2) && + ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { + /* this is two hexadecimal digits following a '%' */ + in = (unsigned char)(onehex2dec(string[1]) << 4) | onehex2dec(string[2]); + + string += 3; + alloc -= 3; + } + else { + string++; + alloc--; + } + + if(((ctrl == REJECT_CTRL) && (in < 0x20)) || + ((ctrl == REJECT_ZERO) && (in == 0))) { + Curl_safefree(*ostring); + return CURLE_URL_MALFORMAT; + } + + *ns++ = in; + } + *ns = 0; /* terminate it */ + + if(olen) + /* store output size */ + *olen = ns - *ostring; + + return CURLE_OK; +} + +/* + * Unescapes the given URL escaped string of given length. Returns a + * pointer to a malloced string with length given in *olen. + * If length == 0, the length is assumed to be strlen(string). + * If olen == NULL, no output length is stored. + * 'data' is ignored since 7.82.0. + */ +char *curl_easy_unescape(struct Curl_easy *data, const char *string, + int length, int *olen) +{ + char *str = NULL; + (void)data; + if(length >= 0) { + size_t inputlen = (size_t)length; + size_t outputlen; + CURLcode res = Curl_urldecode(string, inputlen, &str, &outputlen, + REJECT_NADA); + if(res) + return NULL; + + if(olen) { + if(outputlen <= (size_t) INT_MAX) + *olen = curlx_uztosi(outputlen); + else + /* too large to return in an int, fail! */ + Curl_safefree(str); + } + } + return str; +} + +/* For operating systems/environments that use different malloc/free + systems for the app and for this library, we provide a free that uses + the library's memory system */ +void curl_free(void *p) +{ + free(p); +} + +/* + * Curl_hexencode() + * + * Converts binary input to lowercase hex-encoded ASCII output. + * Null-terminated. + */ +void Curl_hexencode(const unsigned char *src, size_t len, /* input length */ + unsigned char *out, size_t olen) /* output buffer size */ +{ + const char *hex = "0123456789abcdef"; + DEBUGASSERT(src && len && (olen >= 3)); + if(src && len && (olen >= 3)) { + while(len-- && (olen >= 3)) { + /* clang-tidy warns on this line without this comment: */ + /* NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) */ + *out++ = hex[(*src & 0xF0)>>4]; + *out++ = hex[*src & 0x0F]; + ++src; + olen -= 2; + } + *out = 0; + } + else if(olen) + *out = 0; +} diff --git a/Utilities/cmcurl/lib/escape.h b/Utilities/cmcurl/lib/escape.h new file mode 100644 index 0000000..690e417 --- /dev/null +++ b/Utilities/cmcurl/lib/escape.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_ESCAPE_H +#define HEADER_CURL_ESCAPE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* Escape and unescape URL encoding in strings. The functions return a new + * allocated string or NULL if an error occurred. */ + +#include "curl_ctype.h" + +enum urlreject { + REJECT_NADA = 2, + REJECT_CTRL, + REJECT_ZERO +}; + +CURLcode Curl_urldecode(const char *string, size_t length, + char **ostring, size_t *olen, + enum urlreject ctrl); + +void Curl_hexencode(const unsigned char *src, size_t len, /* input length */ + unsigned char *out, size_t olen); /* output buffer size */ + +#endif /* HEADER_CURL_ESCAPE_H */ diff --git a/Utilities/cmcurl/lib/file.c b/Utilities/cmcurl/lib/file.c new file mode 100644 index 0000000..c985071 --- /dev/null +++ b/Utilities/cmcurl/lib/file.c @@ -0,0 +1,583 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_FILE + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#include "strtoofft.h" +#include "urldata.h" +#include <curl/curl.h> +#include "progress.h" +#include "sendf.h" +#include "escape.h" +#include "file.h" +#include "speedcheck.h" +#include "getinfo.h" +#include "transfer.h" +#include "url.h" +#include "parsedate.h" /* for the week day and month names */ +#include "warnless.h" +#include "curl_range.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#if defined(_WIN32) || defined(MSDOS) || defined(__EMX__) +#define DOS_FILESYSTEM 1 +#elif defined(__amigaos4__) +#define AMIGA_FILESYSTEM 1 +#endif + +#ifdef OPEN_NEEDS_ARG3 +# define open_readonly(p,f) open((p),(f),(0)) +#else +# define open_readonly(p,f) open((p),(f)) +#endif + +/* + * Forward declarations. + */ + +static CURLcode file_do(struct Curl_easy *data, bool *done); +static CURLcode file_done(struct Curl_easy *data, + CURLcode status, bool premature); +static CURLcode file_connect(struct Curl_easy *data, bool *done); +static CURLcode file_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection); +static CURLcode file_setup_connection(struct Curl_easy *data, + struct connectdata *conn); + +/* + * FILE scheme handler. + */ + +const struct Curl_handler Curl_handler_file = { + "FILE", /* scheme */ + file_setup_connection, /* setup_connection */ + file_do, /* do_it */ + file_done, /* done */ + ZERO_NULL, /* do_more */ + file_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + file_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + 0, /* defport */ + CURLPROTO_FILE, /* protocol */ + CURLPROTO_FILE, /* family */ + PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */ +}; + + +static CURLcode file_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + (void)conn; + /* allocate the FILE specific struct */ + data->req.p.file = calloc(1, sizeof(struct FILEPROTO)); + if(!data->req.p.file) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +/* + * file_connect() gets called from Curl_protocol_connect() to allow us to + * do protocol-specific actions at connect-time. We emulate a + * connect-then-transfer protocol and "connect" to the file here + */ +static CURLcode file_connect(struct Curl_easy *data, bool *done) +{ + char *real_path; + struct FILEPROTO *file = data->req.p.file; + int fd; +#ifdef DOS_FILESYSTEM + size_t i; + char *actual_path; +#endif + size_t real_path_len; + CURLcode result; + + if(file->path) { + /* already connected. + * the handler->connect_it() is normally only called once, but + * FILE does a special check on setting up the connection which + * calls this explicitly. */ + *done = TRUE; + return CURLE_OK; + } + + result = Curl_urldecode(data->state.up.path, 0, &real_path, + &real_path_len, REJECT_ZERO); + if(result) + return result; + +#ifdef DOS_FILESYSTEM + /* If the first character is a slash, and there's + something that looks like a drive at the beginning of + the path, skip the slash. If we remove the initial + slash in all cases, paths without drive letters end up + relative to the current directory which isn't how + browsers work. + + Some browsers accept | instead of : as the drive letter + separator, so we do too. + + On other platforms, we need the slash to indicate an + absolute pathname. On Windows, absolute paths start + with a drive letter. + */ + actual_path = real_path; + if((actual_path[0] == '/') && + actual_path[1] && + (actual_path[2] == ':' || actual_path[2] == '|')) { + actual_path[2] = ':'; + actual_path++; + real_path_len--; + } + + /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */ + for(i = 0; i < real_path_len; ++i) + if(actual_path[i] == '/') + actual_path[i] = '\\'; + else if(!actual_path[i]) { /* binary zero */ + Curl_safefree(real_path); + return CURLE_URL_MALFORMAT; + } + + fd = open_readonly(actual_path, O_RDONLY|O_BINARY); + file->path = actual_path; +#else + if(memchr(real_path, 0, real_path_len)) { + /* binary zeroes indicate foul play */ + Curl_safefree(real_path); + return CURLE_URL_MALFORMAT; + } + + #ifdef AMIGA_FILESYSTEM + /* + * A leading slash in an AmigaDOS path denotes the parent + * directory, and hence we block this as it is relative. + * Absolute paths start with 'volumename:', so we check for + * this first. Failing that, we treat the path as a real unix + * path, but only if the application was compiled with -lunix. + */ + fd = -1; + file->path = real_path; + + if(real_path[0] == '/') { + extern int __unix_path_semantics; + if(strchr(real_path + 1, ':')) { + /* Amiga absolute path */ + fd = open_readonly(real_path + 1, O_RDONLY); + file->path++; + } + else if(__unix_path_semantics) { + /* -lunix fallback */ + fd = open_readonly(real_path, O_RDONLY); + } + } + #else + fd = open_readonly(real_path, O_RDONLY); + file->path = real_path; + #endif +#endif + Curl_safefree(file->freepath); + file->freepath = real_path; /* free this when done */ + + file->fd = fd; + if(!data->state.upload && (fd == -1)) { + failf(data, "Couldn't open file %s", data->state.up.path); + file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE); + return CURLE_FILE_COULDNT_READ_FILE; + } + *done = TRUE; + + return CURLE_OK; +} + +static CURLcode file_done(struct Curl_easy *data, + CURLcode status, bool premature) +{ + struct FILEPROTO *file = data->req.p.file; + (void)status; /* not used */ + (void)premature; /* not used */ + + if(file) { + Curl_safefree(file->freepath); + file->path = NULL; + if(file->fd != -1) + close(file->fd); + file->fd = -1; + } + + return CURLE_OK; +} + +static CURLcode file_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + (void)dead_connection; /* not used */ + (void)conn; + return file_done(data, CURLE_OK, FALSE); +} + +#ifdef DOS_FILESYSTEM +#define DIRSEP '\\' +#else +#define DIRSEP '/' +#endif + +static CURLcode file_upload(struct Curl_easy *data) +{ + struct FILEPROTO *file = data->req.p.file; + const char *dir = strchr(file->path, DIRSEP); + int fd; + int mode; + CURLcode result = CURLE_OK; + char *buf = data->state.buffer; + curl_off_t bytecount = 0; + struct_stat file_stat; + const char *buf2; + + /* + * Since FILE: doesn't do the full init, we need to provide some extra + * assignments here. + */ + data->req.upload_fromhere = buf; + + if(!dir) + return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ + + if(!dir[1]) + return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ + +#ifdef O_BINARY +#define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY +#else +#define MODE_DEFAULT O_WRONLY|O_CREAT +#endif + + if(data->state.resume_from) + mode = MODE_DEFAULT|O_APPEND; + else + mode = MODE_DEFAULT|O_TRUNC; + + fd = open(file->path, mode, data->set.new_file_perms); + if(fd < 0) { + failf(data, "Can't open %s for writing", file->path); + return CURLE_WRITE_ERROR; + } + + if(-1 != data->state.infilesize) + /* known size of data to "upload" */ + Curl_pgrsSetUploadSize(data, data->state.infilesize); + + /* treat the negative resume offset value as the case of "-" */ + if(data->state.resume_from < 0) { + if(fstat(fd, &file_stat)) { + close(fd); + failf(data, "Can't get the size of %s", file->path); + return CURLE_WRITE_ERROR; + } + data->state.resume_from = (curl_off_t)file_stat.st_size; + } + + while(!result) { + size_t nread; + ssize_t nwrite; + size_t readcount; + result = Curl_fillreadbuffer(data, data->set.buffer_size, &readcount); + if(result) + break; + + if(!readcount) + break; + + nread = readcount; + + /* skip bytes before resume point */ + if(data->state.resume_from) { + if((curl_off_t)nread <= data->state.resume_from) { + data->state.resume_from -= nread; + nread = 0; + buf2 = buf; + } + else { + buf2 = buf + data->state.resume_from; + nread -= (size_t)data->state.resume_from; + data->state.resume_from = 0; + } + } + else + buf2 = buf; + + /* write the data to the target */ + nwrite = write(fd, buf2, nread); + if((size_t)nwrite != nread) { + result = CURLE_SEND_ERROR; + break; + } + + bytecount += nread; + + Curl_pgrsSetUploadCounter(data, bytecount); + + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, Curl_now()); + } + if(!result && Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + + close(fd); + + return result; +} + +/* + * file_do() is the protocol-specific function for the do-phase, separated + * from the connect-phase above. Other protocols merely setup the transfer in + * the do-phase, to have it done in the main transfer loop but since some + * platforms we support don't allow select()ing etc on file handles (as + * opposed to sockets) we instead perform the whole do-operation in this + * function. + */ +static CURLcode file_do(struct Curl_easy *data, bool *done) +{ + /* This implementation ignores the host name 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 + */ + CURLcode result = CURLE_OK; + struct_stat statbuf; /* struct_stat instead of struct stat just to allow the + Windows version to have a different struct without + having to redefine the simple word 'stat' */ + curl_off_t expected_size = -1; + bool size_known; + bool fstated = FALSE; + char *buf = data->state.buffer; + int fd; + struct FILEPROTO *file; + + *done = TRUE; /* unconditionally */ + + Curl_pgrsStartNow(data); + + if(data->state.upload) + return file_upload(data); + + file = data->req.p.file; + + /* get the fd from the connection phase */ + fd = file->fd; + + /* VMS: This only works reliable for STREAMLF files */ + if(-1 != fstat(fd, &statbuf)) { + if(!S_ISDIR(statbuf.st_mode)) + expected_size = statbuf.st_size; + /* and store the modification time */ + data->info.filetime = statbuf.st_mtime; + fstated = TRUE; + } + + if(fstated && !data->state.range && data->set.timecondition) { + if(!Curl_meets_timecondition(data, data->info.filetime)) { + *done = TRUE; + return CURLE_OK; + } + } + + if(fstated) { + time_t filetime; + struct tm buffer; + const struct tm *tm = &buffer; + char header[80]; + int headerlen; + char accept_ranges[24]= { "Accept-ranges: bytes\r\n" }; + if(expected_size >= 0) { + headerlen = msnprintf(header, sizeof(header), + "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", + expected_size); + result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen); + if(result) + return result; + + result = Curl_client_write(data, CLIENTWRITE_HEADER, + accept_ranges, strlen(accept_ranges)); + if(result != CURLE_OK) + return result; + } + + filetime = (time_t)statbuf.st_mtime; + result = Curl_gmtime(filetime, &buffer); + if(result) + return result; + + /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ + headerlen = msnprintf(header, sizeof(header), + "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n%s", + Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec, + data->req.no_body ? "": "\r\n"); + result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen); + if(result) + return result; + /* set the file size to make it available post transfer */ + Curl_pgrsSetDownloadSize(data, expected_size); + if(data->req.no_body) + return result; + } + + /* Check whether file range has been specified */ + result = Curl_range(data); + if(result) + return result; + + /* Adjust the start offset in case we want to get the N last bytes + * of the stream if the filesize could be determined */ + if(data->state.resume_from < 0) { + if(!fstated) { + failf(data, "Can't get the size of file."); + return CURLE_READ_ERROR; + } + data->state.resume_from += (curl_off_t)statbuf.st_size; + } + + if(data->state.resume_from > 0) { + /* We check explicitly if we have a start offset, because + * expected_size may be -1 if we don't know how large the file is, + * in which case we should not adjust it. */ + if(data->state.resume_from <= expected_size) + expected_size -= data->state.resume_from; + else { + failf(data, "failed to resume file:// transfer"); + return CURLE_BAD_DOWNLOAD_RESUME; + } + } + + /* A high water mark has been specified so we obey... */ + if(data->req.maxdownload > 0) + expected_size = data->req.maxdownload; + + if(!fstated || (expected_size <= 0)) + size_known = FALSE; + else + size_known = TRUE; + + /* The following is a shortcut implementation of file reading + this is both more efficient than the former call to download() and + it avoids problems with select() and recv() on file descriptors + in Winsock */ + if(size_known) + Curl_pgrsSetDownloadSize(data, expected_size); + + if(data->state.resume_from) { + if(data->state.resume_from != + lseek(fd, data->state.resume_from, SEEK_SET)) + return CURLE_BAD_DOWNLOAD_RESUME; + } + + Curl_pgrsTime(data, TIMER_STARTTRANSFER); + + while(!result) { + ssize_t nread; + /* Don't fill a whole buffer if we want less than all data */ + size_t bytestoread; + + if(size_known) { + bytestoread = (expected_size < data->set.buffer_size) ? + curlx_sotouz(expected_size) : (size_t)data->set.buffer_size; + } + else + bytestoread = data->set.buffer_size-1; + + nread = read(fd, buf, bytestoread); + + if(nread > 0) + buf[nread] = 0; + + if(nread <= 0 || (size_known && (expected_size == 0))) + break; + + if(size_known) + expected_size -= nread; + + result = Curl_client_write(data, CLIENTWRITE_BODY, buf, nread); + if(result) + return result; + + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, Curl_now()); + } + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + + return result; +} + +#endif diff --git a/Utilities/cmcurl/lib/file.h b/Utilities/cmcurl/lib/file.h new file mode 100644 index 0000000..4565525 --- /dev/null +++ b/Utilities/cmcurl/lib/file.h @@ -0,0 +1,42 @@ +#ifndef HEADER_CURL_FILE_H +#define HEADER_CURL_FILE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + + +/**************************************************************************** + * FILE unique setup + ***************************************************************************/ +struct FILEPROTO { + char *path; /* the path we operate on */ + 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! */ +}; + +#ifndef CURL_DISABLE_FILE +extern const struct Curl_handler Curl_handler_file; +#endif + +#endif /* HEADER_CURL_FILE_H */ diff --git a/Utilities/cmcurl/lib/fileinfo.c b/Utilities/cmcurl/lib/fileinfo.c new file mode 100644 index 0000000..2be3b32 --- /dev/null +++ b/Utilities/cmcurl/lib/fileinfo.c @@ -0,0 +1,46 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#ifndef CURL_DISABLE_FTP +#include "strdup.h" +#include "fileinfo.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +struct fileinfo *Curl_fileinfo_alloc(void) +{ + return calloc(1, sizeof(struct fileinfo)); +} + +void Curl_fileinfo_cleanup(struct fileinfo *finfo) +{ + if(!finfo) + return; + + Curl_dyn_free(&finfo->buf); + free(finfo); +} +#endif diff --git a/Utilities/cmcurl/lib/fileinfo.h b/Utilities/cmcurl/lib/fileinfo.h new file mode 100644 index 0000000..ce009da --- /dev/null +++ b/Utilities/cmcurl/lib/fileinfo.h @@ -0,0 +1,40 @@ +#ifndef HEADER_CURL_FILEINFO_H +#define HEADER_CURL_FILEINFO_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include <curl/curl.h> +#include "llist.h" +#include "dynbuf.h" + +struct fileinfo { + struct curl_fileinfo info; + struct Curl_llist_element list; + struct dynbuf buf; +}; + +struct fileinfo *Curl_fileinfo_alloc(void); +void Curl_fileinfo_cleanup(struct fileinfo *finfo); + +#endif /* HEADER_CURL_FILEINFO_H */ diff --git a/Utilities/cmcurl/lib/fopen.c b/Utilities/cmcurl/lib/fopen.c new file mode 100644 index 0000000..851279f --- /dev/null +++ b/Utilities/cmcurl/lib/fopen.c @@ -0,0 +1,153 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_COOKIES) || !defined(CURL_DISABLE_ALTSVC) || \ + !defined(CURL_DISABLE_HSTS) + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#include "urldata.h" +#include "rand.h" +#include "fopen.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.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. + + This function returns a pointer to malloc'ed memory. + + The input path to this function is expected to have a file name part. +*/ + +#ifdef _WIN32 +#define PATHSEP "\\" +#define IS_SEP(x) (((x) == '/') || ((x) == '\\')) +#elif defined(MSDOS) || defined(__EMX__) || defined(OS2) +#define PATHSEP "\\" +#define IS_SEP(x) ((x) == '\\') +#else +#define PATHSEP "/" +#define IS_SEP(x) ((x) == '/') +#endif + +static char *dirslash(const char *path) +{ + size_t n; + struct dynbuf out; + DEBUGASSERT(path); + Curl_dyn_init(&out, CURL_MAX_INPUT_LENGTH); + n = strlen(path); + if(n) { + /* find the rightmost path separator, if any */ + while(n && !IS_SEP(path[n-1])) + --n; + /* skip over all the path separators, if any */ + while(n && IS_SEP(path[n-1])) + --n; + } + if(Curl_dyn_addn(&out, path, n)) + return NULL; + /* if there was a directory, append a single trailing slash */ + if(n && Curl_dyn_addn(&out, PATHSEP, 1)) + return NULL; + return Curl_dyn_ptr(&out); +} + +/* + * Curl_fopen() opens a file for writing with a temp name, to be renamed + * to the final name when completed. If there is an existing file using this + * name at the time of the open, this function will clone the mode from that + * file. if 'tempname' is non-NULL, it needs a rename after the file is + * written. + */ +CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, + FILE **fh, char **tempname) +{ + CURLcode result = CURLE_WRITE_ERROR; + unsigned char randbuf[41]; + char *tempstore = NULL; + struct_stat sb; + int fd = -1; + char *dir = NULL; + *tempname = NULL; + + *fh = fopen(filename, FOPEN_WRITETEXT); + if(!*fh) + goto fail; + if(fstat(fileno(*fh), &sb) == -1 || !S_ISREG(sb.st_mode)) { + return CURLE_OK; + } + fclose(*fh); + *fh = NULL; + + result = Curl_rand_alnum(data, randbuf, sizeof(randbuf)); + if(result) + goto fail; + + dir = dirslash(filename); + if(dir) { + /* The temp file name should not end up too long for the target file + system */ + tempstore = aprintf("%s%s.tmp", dir, randbuf); + free(dir); + } + + if(!tempstore) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + + result = CURLE_WRITE_ERROR; + fd = open(tempstore, O_WRONLY | O_CREAT | O_EXCL, 0600|sb.st_mode); + if(fd == -1) + goto fail; + + *fh = fdopen(fd, FOPEN_WRITETEXT); + if(!*fh) + goto fail; + + *tempname = tempstore; + return CURLE_OK; + +fail: + if(fd != -1) { + close(fd); + unlink(tempstore); + } + + free(tempstore); + return result; +} + +#endif /* ! disabled */ diff --git a/Utilities/cmcurl/lib/fopen.h b/Utilities/cmcurl/lib/fopen.h new file mode 100644 index 0000000..e3a919d --- /dev/null +++ b/Utilities/cmcurl/lib/fopen.h @@ -0,0 +1,30 @@ +#ifndef HEADER_CURL_FOPEN_H +#define HEADER_CURL_FOPEN_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, + FILE **fh, char **tempname); + +#endif diff --git a/Utilities/cmcurl/lib/formdata.c b/Utilities/cmcurl/lib/formdata.c new file mode 100644 index 0000000..05dc9b5 --- /dev/null +++ b/Utilities/cmcurl/lib/formdata.c @@ -0,0 +1,960 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "formdata.h" +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API) + +#if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME) +#include <libgen.h> +#endif + +#include "urldata.h" /* for struct Curl_easy */ +#include "mime.h" +#include "vtls/vtls.h" +#include "strcase.h" +#include "sendf.h" +#include "strdup.h" +#include "rand.h" +#include "warnless.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +#define HTTPPOST_PTRNAME CURL_HTTPPOST_PTRNAME +#define HTTPPOST_FILENAME CURL_HTTPPOST_FILENAME +#define HTTPPOST_PTRCONTENTS CURL_HTTPPOST_PTRCONTENTS +#define HTTPPOST_READFILE CURL_HTTPPOST_READFILE +#define HTTPPOST_PTRBUFFER CURL_HTTPPOST_PTRBUFFER +#define HTTPPOST_CALLBACK CURL_HTTPPOST_CALLBACK +#define HTTPPOST_BUFFER CURL_HTTPPOST_BUFFER + +/*************************************************************************** + * + * AddHttpPost() + * + * Adds an HttpPost structure to the list, if parent_post is given becomes + * a subpost of parent_post instead of a direct list element. + * + * Returns newly allocated HttpPost on success and NULL if malloc failed. + * + ***************************************************************************/ +static struct curl_httppost * +AddHttpPost(char *name, size_t namelength, + char *value, curl_off_t contentslength, + char *buffer, size_t bufferlength, + char *contenttype, + long flags, + struct curl_slist *contentHeader, + char *showfilename, char *userp, + struct curl_httppost *parent_post, + struct curl_httppost **httppost, + struct curl_httppost **last_post) +{ + struct curl_httppost *post; + if(!namelength && name) + namelength = strlen(name); + if((bufferlength > LONG_MAX) || (namelength > LONG_MAX)) + /* avoid overflow in typecasts below */ + return NULL; + post = calloc(1, sizeof(struct curl_httppost)); + if(post) { + post->name = name; + post->namelength = (long)namelength; + post->contents = value; + post->contentlen = contentslength; + post->buffer = buffer; + post->bufferlength = (long)bufferlength; + post->contenttype = contenttype; + post->contentheader = contentHeader; + post->showfilename = showfilename; + post->userp = userp; + post->flags = flags | CURL_HTTPPOST_LARGE; + } + else + return NULL; + + if(parent_post) { + /* now, point our 'more' to the original 'more' */ + post->more = parent_post->more; + + /* then move the original 'more' to point to ourselves */ + parent_post->more = post; + } + else { + /* make the previous point to this */ + if(*last_post) + (*last_post)->next = post; + else + (*httppost) = post; + + (*last_post) = post; + } + return post; +} + +/*************************************************************************** + * + * AddFormInfo() + * + * Adds a FormInfo structure to the list presented by parent_form_info. + * + * Returns newly allocated FormInfo on success and NULL if malloc failed/ + * parent_form_info is NULL. + * + ***************************************************************************/ +static struct FormInfo *AddFormInfo(char *value, + char *contenttype, + struct FormInfo *parent_form_info) +{ + struct FormInfo *form_info; + form_info = calloc(1, sizeof(struct FormInfo)); + if(!form_info) + return NULL; + if(value) + form_info->value = value; + if(contenttype) + form_info->contenttype = contenttype; + form_info->flags = HTTPPOST_FILENAME; + + if(parent_form_info) { + /* now, point our 'more' to the original 'more' */ + form_info->more = parent_form_info->more; + + /* then move the original 'more' to point to ourselves */ + parent_form_info->more = form_info; + } + + return form_info; +} + +/*************************************************************************** + * + * FormAdd() + * + * Stores a formpost parameter and builds the appropriate linked list. + * + * Has two principal functionalities: using files and byte arrays as + * post parts. Byte arrays are either copied or just the pointer is stored + * (as the user requests) while for files only the filename and not the + * content is stored. + * + * While you may have only one byte array for each name, multiple filenames + * are allowed (and because of this feature CURLFORM_END is needed after + * using CURLFORM_FILE). + * + * Examples: + * + * Simple name/value pair with copied contents: + * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", + * CURLFORM_COPYCONTENTS, "value", CURLFORM_END); + * + * name/value pair where only the content pointer is remembered: + * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", + * CURLFORM_PTRCONTENTS, ptr, CURLFORM_CONTENTSLENGTH, 10, CURLFORM_END); + * (if CURLFORM_CONTENTSLENGTH is missing strlen () is used) + * + * storing a filename (CONTENTTYPE is optional!): + * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", + * CURLFORM_FILE, "filename1", CURLFORM_CONTENTTYPE, "plain/text", + * CURLFORM_END); + * + * storing multiple filenames: + * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", + * CURLFORM_FILE, "filename1", CURLFORM_FILE, "filename2", CURLFORM_END); + * + * Returns: + * CURL_FORMADD_OK on success + * CURL_FORMADD_MEMORY if the FormInfo allocation fails + * CURL_FORMADD_OPTION_TWICE if one option is given twice for one Form + * CURL_FORMADD_NULL if a null pointer was given for a char + * CURL_FORMADD_MEMORY if the allocation of a FormInfo struct failed + * CURL_FORMADD_UNKNOWN_OPTION if an unknown option was used + * CURL_FORMADD_INCOMPLETE if the some FormInfo is not complete (or error) + * CURL_FORMADD_MEMORY if an HttpPost struct cannot be allocated + * CURL_FORMADD_MEMORY if some allocation for string copying failed. + * CURL_FORMADD_ILLEGAL_ARRAY if an illegal option is used in an array + * + ***************************************************************************/ + +static +CURLFORMcode FormAdd(struct curl_httppost **httppost, + struct curl_httppost **last_post, + va_list params) +{ + struct FormInfo *first_form, *current_form, *form = NULL; + CURLFORMcode return_value = CURL_FORMADD_OK; + const char *prevtype = NULL; + struct curl_httppost *post = NULL; + CURLformoption option; + struct curl_forms *forms = NULL; + char *array_value = NULL; /* value read from an array */ + + /* This is a state variable, that if TRUE means that we're parsing an + array that we got passed to us. If FALSE we're parsing the input + va_list arguments. */ + bool array_state = FALSE; + + /* + * We need to allocate the first struct to fill in. + */ + first_form = calloc(1, sizeof(struct FormInfo)); + if(!first_form) + return CURL_FORMADD_MEMORY; + + current_form = first_form; + + /* + * Loop through all the options set. Break if we have an error to report. + */ + while(return_value == CURL_FORMADD_OK) { + + /* first see if we have more parts of the array param */ + if(array_state && forms) { + /* get the upcoming option from the given array */ + option = forms->option; + array_value = (char *)forms->value; + + forms++; /* advance this to next entry */ + if(CURLFORM_END == option) { + /* end of array state */ + array_state = FALSE; + continue; + } + } + else { + /* This is not array-state, get next option. This gets an 'int' with + va_arg() because CURLformoption might be a smaller type than int and + might cause compiler warnings and wrong behavior. */ + option = (CURLformoption)va_arg(params, int); + if(CURLFORM_END == option) + break; + } + + switch(option) { + case CURLFORM_ARRAY: + if(array_state) + /* we don't support an array from within an array */ + return_value = CURL_FORMADD_ILLEGAL_ARRAY; + else { + forms = va_arg(params, struct curl_forms *); + if(forms) + array_state = TRUE; + else + return_value = CURL_FORMADD_NULL; + } + break; + + /* + * Set the Name property. + */ + case CURLFORM_PTRNAME: + current_form->flags |= HTTPPOST_PTRNAME; /* fall through */ + + /* FALLTHROUGH */ + case CURLFORM_COPYNAME: + if(current_form->name) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + char *name = array_state? + array_value:va_arg(params, char *); + if(name) + current_form->name = name; /* store for the moment */ + else + return_value = CURL_FORMADD_NULL; + } + break; + case CURLFORM_NAMELENGTH: + if(current_form->namelength) + return_value = CURL_FORMADD_OPTION_TWICE; + else + current_form->namelength = + array_state?(size_t)array_value:(size_t)va_arg(params, long); + break; + + /* + * Set the contents property. + */ + case CURLFORM_PTRCONTENTS: + current_form->flags |= HTTPPOST_PTRCONTENTS; + /* FALLTHROUGH */ + case CURLFORM_COPYCONTENTS: + if(current_form->value) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + char *value = + array_state?array_value:va_arg(params, char *); + if(value) + current_form->value = value; /* store for the moment */ + else + return_value = CURL_FORMADD_NULL; + } + break; + case CURLFORM_CONTENTSLENGTH: + current_form->contentslength = + array_state?(size_t)array_value:(size_t)va_arg(params, long); + break; + + case CURLFORM_CONTENTLEN: + current_form->flags |= CURL_HTTPPOST_LARGE; + current_form->contentslength = + array_state?(curl_off_t)(size_t)array_value:va_arg(params, curl_off_t); + break; + + /* Get contents from a given file name */ + case CURLFORM_FILECONTENT: + if(current_form->flags & (HTTPPOST_PTRCONTENTS|HTTPPOST_READFILE)) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + const char *filename = array_state? + array_value:va_arg(params, char *); + if(filename) { + current_form->value = strdup(filename); + if(!current_form->value) + return_value = CURL_FORMADD_MEMORY; + else { + current_form->flags |= HTTPPOST_READFILE; + current_form->value_alloc = TRUE; + } + } + else + return_value = CURL_FORMADD_NULL; + } + break; + + /* We upload a file */ + case CURLFORM_FILE: + { + const char *filename = array_state?array_value: + va_arg(params, char *); + + if(current_form->value) { + if(current_form->flags & HTTPPOST_FILENAME) { + if(filename) { + char *fname = strdup(filename); + if(!fname) + return_value = CURL_FORMADD_MEMORY; + else { + form = AddFormInfo(fname, NULL, current_form); + if(!form) { + free(fname); + return_value = CURL_FORMADD_MEMORY; + } + else { + form->value_alloc = TRUE; + current_form = form; + form = NULL; + } + } + } + else + return_value = CURL_FORMADD_NULL; + } + else + return_value = CURL_FORMADD_OPTION_TWICE; + } + else { + if(filename) { + current_form->value = strdup(filename); + if(!current_form->value) + return_value = CURL_FORMADD_MEMORY; + else { + current_form->flags |= HTTPPOST_FILENAME; + current_form->value_alloc = TRUE; + } + } + else + return_value = CURL_FORMADD_NULL; + } + break; + } + + case CURLFORM_BUFFERPTR: + current_form->flags |= HTTPPOST_PTRBUFFER|HTTPPOST_BUFFER; + if(current_form->buffer) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + char *buffer = + array_state?array_value:va_arg(params, char *); + if(buffer) { + current_form->buffer = buffer; /* store for the moment */ + current_form->value = buffer; /* make it non-NULL to be accepted + as fine */ + } + else + return_value = CURL_FORMADD_NULL; + } + break; + + case CURLFORM_BUFFERLENGTH: + if(current_form->bufferlength) + return_value = CURL_FORMADD_OPTION_TWICE; + else + current_form->bufferlength = + array_state?(size_t)array_value:(size_t)va_arg(params, long); + break; + + case CURLFORM_STREAM: + current_form->flags |= HTTPPOST_CALLBACK; + if(current_form->userp) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + char *userp = + array_state?array_value:va_arg(params, char *); + if(userp) { + current_form->userp = userp; + current_form->value = userp; /* this isn't strictly true but we + derive a value from this later on + and we need this non-NULL to be + accepted as a fine form part */ + } + else + return_value = CURL_FORMADD_NULL; + } + break; + + case CURLFORM_CONTENTTYPE: + { + const char *contenttype = + array_state?array_value:va_arg(params, char *); + if(current_form->contenttype) { + if(current_form->flags & HTTPPOST_FILENAME) { + if(contenttype) { + char *type = strdup(contenttype); + if(!type) + return_value = CURL_FORMADD_MEMORY; + else { + form = AddFormInfo(NULL, type, current_form); + if(!form) { + free(type); + return_value = CURL_FORMADD_MEMORY; + } + else { + form->contenttype_alloc = TRUE; + current_form = form; + form = NULL; + } + } + } + else + return_value = CURL_FORMADD_NULL; + } + else + return_value = CURL_FORMADD_OPTION_TWICE; + } + else { + if(contenttype) { + current_form->contenttype = strdup(contenttype); + if(!current_form->contenttype) + return_value = CURL_FORMADD_MEMORY; + else + current_form->contenttype_alloc = TRUE; + } + else + return_value = CURL_FORMADD_NULL; + } + break; + } + case CURLFORM_CONTENTHEADER: + { + /* this "cast increases required alignment of target type" but + we consider it OK anyway */ + struct curl_slist *list = array_state? + (struct curl_slist *)(void *)array_value: + va_arg(params, struct curl_slist *); + + if(current_form->contentheader) + return_value = CURL_FORMADD_OPTION_TWICE; + else + current_form->contentheader = list; + + break; + } + case CURLFORM_FILENAME: + case CURLFORM_BUFFER: + { + const char *filename = array_state?array_value: + va_arg(params, char *); + if(current_form->showfilename) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + current_form->showfilename = strdup(filename); + if(!current_form->showfilename) + return_value = CURL_FORMADD_MEMORY; + else + current_form->showfilename_alloc = TRUE; + } + break; + } + default: + return_value = CURL_FORMADD_UNKNOWN_OPTION; + break; + } + } + + if(CURL_FORMADD_OK != return_value) { + /* On error, free allocated fields for all nodes of the FormInfo linked + list without deallocating nodes. List nodes are deallocated later on */ + struct FormInfo *ptr; + for(ptr = first_form; ptr != NULL; ptr = ptr->more) { + if(ptr->name_alloc) { + Curl_safefree(ptr->name); + ptr->name_alloc = FALSE; + } + if(ptr->value_alloc) { + Curl_safefree(ptr->value); + ptr->value_alloc = FALSE; + } + if(ptr->contenttype_alloc) { + Curl_safefree(ptr->contenttype); + ptr->contenttype_alloc = FALSE; + } + if(ptr->showfilename_alloc) { + Curl_safefree(ptr->showfilename); + ptr->showfilename_alloc = FALSE; + } + } + } + + if(CURL_FORMADD_OK == return_value) { + /* go through the list, check for completeness and if everything is + * alright add the HttpPost item otherwise set return_value accordingly */ + + post = NULL; + for(form = first_form; + form != NULL; + form = form->more) { + if(((!form->name || !form->value) && !post) || + ( (form->contentslength) && + (form->flags & HTTPPOST_FILENAME) ) || + ( (form->flags & HTTPPOST_FILENAME) && + (form->flags & HTTPPOST_PTRCONTENTS) ) || + + ( (!form->buffer) && + (form->flags & HTTPPOST_BUFFER) && + (form->flags & HTTPPOST_PTRBUFFER) ) || + + ( (form->flags & HTTPPOST_READFILE) && + (form->flags & HTTPPOST_PTRCONTENTS) ) + ) { + return_value = CURL_FORMADD_INCOMPLETE; + break; + } + if(((form->flags & HTTPPOST_FILENAME) || + (form->flags & HTTPPOST_BUFFER)) && + !form->contenttype) { + char *f = (form->flags & HTTPPOST_BUFFER)? + form->showfilename : form->value; + char const *type; + type = Curl_mime_contenttype(f); + if(!type) + type = prevtype; + if(!type) + type = FILE_CONTENTTYPE_DEFAULT; + + /* our contenttype is missing */ + form->contenttype = strdup(type); + if(!form->contenttype) { + return_value = CURL_FORMADD_MEMORY; + break; + } + form->contenttype_alloc = TRUE; + } + if(form->name && form->namelength) { + /* Name should not contain nul bytes. */ + size_t i; + for(i = 0; i < form->namelength; i++) + if(!form->name[i]) { + return_value = CURL_FORMADD_NULL; + break; + } + if(return_value != CURL_FORMADD_OK) + break; + } + if(!(form->flags & HTTPPOST_PTRNAME) && + (form == first_form) ) { + /* Note that there's small risk that form->name is NULL here if the + app passed in a bad combo, so we better check for that first. */ + if(form->name) { + /* copy name (without strdup; possibly not null-terminated) */ + form->name = Curl_strndup(form->name, form->namelength? + form->namelength: + strlen(form->name)); + } + if(!form->name) { + return_value = CURL_FORMADD_MEMORY; + break; + } + form->name_alloc = TRUE; + } + if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE | + HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER | + HTTPPOST_CALLBACK)) && form->value) { + /* copy value (without strdup; possibly contains null characters) */ + size_t clen = (size_t) form->contentslength; + if(!clen) + clen = strlen(form->value) + 1; + + form->value = Curl_memdup(form->value, clen); + + if(!form->value) { + return_value = CURL_FORMADD_MEMORY; + break; + } + form->value_alloc = TRUE; + } + post = AddHttpPost(form->name, form->namelength, + form->value, form->contentslength, + form->buffer, form->bufferlength, + form->contenttype, form->flags, + form->contentheader, form->showfilename, + form->userp, + post, httppost, + last_post); + + if(!post) { + return_value = CURL_FORMADD_MEMORY; + break; + } + + if(form->contenttype) + prevtype = form->contenttype; + } + if(CURL_FORMADD_OK != return_value) { + /* On error, free allocated fields for nodes of the FormInfo linked + list which are not already owned by the httppost linked list + without deallocating nodes. List nodes are deallocated later on */ + struct FormInfo *ptr; + for(ptr = form; ptr != NULL; ptr = ptr->more) { + if(ptr->name_alloc) { + Curl_safefree(ptr->name); + ptr->name_alloc = FALSE; + } + if(ptr->value_alloc) { + Curl_safefree(ptr->value); + ptr->value_alloc = FALSE; + } + if(ptr->contenttype_alloc) { + Curl_safefree(ptr->contenttype); + ptr->contenttype_alloc = FALSE; + } + if(ptr->showfilename_alloc) { + Curl_safefree(ptr->showfilename); + ptr->showfilename_alloc = FALSE; + } + } + } + } + + /* Always deallocate FormInfo linked list nodes without touching node + fields given that these have either been deallocated or are owned + now by the httppost linked list */ + while(first_form) { + struct FormInfo *ptr = first_form->more; + free(first_form); + first_form = ptr; + } + + return return_value; +} + +/* + * curl_formadd() is a public API to add a section to the multipart formpost. + * + * @unittest: 1308 + */ + +CURLFORMcode curl_formadd(struct curl_httppost **httppost, + struct curl_httppost **last_post, + ...) +{ + va_list arg; + CURLFORMcode result; + va_start(arg, last_post); + result = FormAdd(httppost, last_post, arg); + va_end(arg); + return result; +} + +/* + * curl_formget() + * Serialize a curl_httppost struct. + * Returns 0 on success. + * + * @unittest: 1308 + */ +int curl_formget(struct curl_httppost *form, void *arg, + curl_formget_callback append) +{ + CURLcode result; + curl_mimepart toppart; + + Curl_mime_initpart(&toppart); /* default form is empty */ + result = Curl_getformdata(NULL, &toppart, form, NULL); + if(!result) + result = Curl_mime_prepare_headers(NULL, &toppart, "multipart/form-data", + NULL, MIMESTRATEGY_FORM); + + while(!result) { + char buffer[8192]; + size_t nread = Curl_mime_read(buffer, 1, sizeof(buffer), &toppart); + + if(!nread) + break; + + if(nread > sizeof(buffer) || append(arg, buffer, nread) != nread) { + result = CURLE_READ_ERROR; + if(nread == CURL_READFUNC_ABORT) + result = CURLE_ABORTED_BY_CALLBACK; + } + } + + Curl_mime_cleanpart(&toppart); + return (int) result; +} + +/* + * curl_formfree() is an external function to free up a whole form post + * chain + */ +void curl_formfree(struct curl_httppost *form) +{ + struct curl_httppost *next; + + if(!form) + /* no form to free, just get out of this */ + return; + + do { + next = form->next; /* the following form line */ + + /* recurse to sub-contents */ + curl_formfree(form->more); + + if(!(form->flags & HTTPPOST_PTRNAME)) + free(form->name); /* free the name */ + if(!(form->flags & + (HTTPPOST_PTRCONTENTS|HTTPPOST_BUFFER|HTTPPOST_CALLBACK)) + ) + free(form->contents); /* free the contents */ + free(form->contenttype); /* free the content type */ + free(form->showfilename); /* free the faked file name */ + free(form); /* free the struct */ + form = next; + } while(form); /* continue */ +} + + +/* Set mime part name, taking care of non null-terminated name string. */ +static CURLcode setname(curl_mimepart *part, const char *name, size_t len) +{ + char *zname; + CURLcode res; + + if(!name || !len) + return curl_mime_name(part, name); + zname = malloc(len + 1); + if(!zname) + return CURLE_OUT_OF_MEMORY; + memcpy(zname, name, len); + zname[len] = '\0'; + res = curl_mime_name(part, zname); + free(zname); + return res; +} + +/* wrap call to fseeko so it matches the calling convention of callback */ +static int fseeko_wrapper(void *stream, curl_off_t offset, int whence) +{ +#if defined(HAVE_FSEEKO) && defined(HAVE_DECL_FSEEKO) + return fseeko(stream, (off_t)offset, whence); +#elif defined(HAVE__FSEEKI64) + return _fseeki64(stream, (__int64)offset, whence); +#else + if(offset > LONG_MAX) + return -1; + return fseek(stream, (long)offset, whence); +#endif +} + +/* + * Curl_getformdata() converts a linked list of "meta data" into a mime + * structure. The input list is in 'post', while the output is stored in + * mime part at '*finalform'. + * + * This function will not do a failf() for the potential memory failures but + * should for all other errors it spots. Just note that this function MAY get + * a NULL pointer in the 'data' argument. + */ + +CURLcode Curl_getformdata(struct Curl_easy *data, + curl_mimepart *finalform, + struct curl_httppost *post, + curl_read_callback fread_func) +{ + CURLcode result = CURLE_OK; + curl_mime *form = NULL; + curl_mimepart *part; + struct curl_httppost *file; + + Curl_mime_cleanpart(finalform); /* default form is empty */ + + if(!post) + return result; /* no input => no output! */ + + form = curl_mime_init(data); + if(!form) + result = CURLE_OUT_OF_MEMORY; + + if(!result) + result = curl_mime_subparts(finalform, form); + + /* Process each top part. */ + for(; !result && post; post = post->next) { + /* If we have more than a file here, create a mime subpart and fill it. */ + curl_mime *multipart = form; + if(post->more) { + part = curl_mime_addpart(form); + if(!part) + result = CURLE_OUT_OF_MEMORY; + if(!result) + result = setname(part, post->name, post->namelength); + if(!result) { + multipart = curl_mime_init(data); + if(!multipart) + result = CURLE_OUT_OF_MEMORY; + } + if(!result) + result = curl_mime_subparts(part, multipart); + } + + /* Generate all the part contents. */ + for(file = post; !result && file; file = file->more) { + /* Create the part. */ + part = curl_mime_addpart(multipart); + if(!part) + result = CURLE_OUT_OF_MEMORY; + + /* Set the headers. */ + if(!result) + result = curl_mime_headers(part, file->contentheader, 0); + + /* Set the content type. */ + if(!result && file->contenttype) + result = curl_mime_type(part, file->contenttype); + + /* Set field name. */ + if(!result && !post->more) + result = setname(part, post->name, post->namelength); + + /* Process contents. */ + if(!result) { + curl_off_t clen = post->contentslength; + + if(post->flags & CURL_HTTPPOST_LARGE) + clen = post->contentlen; + + if(post->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE)) { + if(!strcmp(file->contents, "-")) { + /* There are a few cases where the code below won't work; in + particular, freopen(stdin) by the caller is not guaranteed + to result as expected. This feature has been kept for backward + compatibility: use of "-" pseudo file name should be avoided. */ + result = curl_mime_data_cb(part, (curl_off_t) -1, + (curl_read_callback) fread, + fseeko_wrapper, + NULL, (void *) stdin); + } + else + result = curl_mime_filedata(part, file->contents); + if(!result && (post->flags & HTTPPOST_READFILE)) + result = curl_mime_filename(part, NULL); + } + else if(post->flags & HTTPPOST_BUFFER) + result = curl_mime_data(part, post->buffer, + post->bufferlength? post->bufferlength: -1); + else if(post->flags & HTTPPOST_CALLBACK) { + /* the contents should be read with the callback and the size is set + with the contentslength */ + if(!clen) + clen = -1; + result = curl_mime_data_cb(part, clen, + fread_func, NULL, NULL, post->userp); + } + else { + size_t uclen; + if(!clen) + uclen = CURL_ZERO_TERMINATED; + else + uclen = (size_t)clen; + result = curl_mime_data(part, post->contents, uclen); + } + } + + /* Set fake file name. */ + if(!result && post->showfilename) + if(post->more || (post->flags & (HTTPPOST_FILENAME | HTTPPOST_BUFFER | + HTTPPOST_CALLBACK))) + result = curl_mime_filename(part, post->showfilename); + } + } + + if(result) + Curl_mime_cleanpart(finalform); + + return result; +} + +#else +/* if disabled */ +CURLFORMcode curl_formadd(struct curl_httppost **httppost, + struct curl_httppost **last_post, + ...) +{ + (void)httppost; + (void)last_post; + return CURL_FORMADD_DISABLED; +} + +int curl_formget(struct curl_httppost *form, void *arg, + curl_formget_callback append) +{ + (void) form; + (void) arg; + (void) append; + return CURL_FORMADD_DISABLED; +} + +void curl_formfree(struct curl_httppost *form) +{ + (void)form; + /* Nothing to do. */ +} + +#endif /* if disabled */ diff --git a/Utilities/cmcurl/lib/formdata.h b/Utilities/cmcurl/lib/formdata.h new file mode 100644 index 0000000..af46624 --- /dev/null +++ b/Utilities/cmcurl/lib/formdata.h @@ -0,0 +1,59 @@ +#ifndef HEADER_CURL_FORMDATA_H +#define HEADER_CURL_FORMDATA_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_FORM_API + +/* used by FormAdd for temporary storage */ +struct FormInfo { + char *name; + size_t namelength; + char *value; + curl_off_t contentslength; + char *contenttype; + long flags; + char *buffer; /* pointer to existing buffer used for file upload */ + size_t bufferlength; + char *showfilename; /* The file name to show. If not set, the actual + file name will be used */ + char *userp; /* pointer for the read callback */ + struct curl_slist *contentheader; + struct FormInfo *more; + bool name_alloc; + bool value_alloc; + bool contenttype_alloc; + bool showfilename_alloc; +}; + +CURLcode Curl_getformdata(struct Curl_easy *data, + curl_mimepart *, + struct curl_httppost *post, + curl_read_callback fread_func); +#endif /* CURL_DISABLE_FORM_API */ + + +#endif /* HEADER_CURL_FORMDATA_H */ diff --git a/Utilities/cmcurl/lib/ftp.c b/Utilities/cmcurl/lib/ftp.c new file mode 100644 index 0000000..28e8ca4 --- /dev/null +++ b/Utilities/cmcurl/lib/ftp.c @@ -0,0 +1,4447 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_FTP + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include <curl/curl.h> +#include "urldata.h" +#include "sendf.h" +#include "if2ip.h" +#include "hostip.h" +#include "progress.h" +#include "transfer.h" +#include "escape.h" +#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "ftp.h" +#include "fileinfo.h" +#include "ftplistparser.h" +#include "curl_range.h" +#include "curl_krb5.h" +#include "strtoofft.h" +#include "strcase.h" +#include "vtls/vtls.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "connect.h" +#include "strerror.h" +#include "inet_ntop.h" +#include "inet_pton.h" +#include "select.h" +#include "parsedate.h" /* for the week day and month names */ +#include "sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "multiif.h" +#include "url.h" +#include "speedcheck.h" +#include "warnless.h" +#include "http_proxy.h" +#include "socks.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif +#ifndef INET_ADDRSTRLEN +#define INET_ADDRSTRLEN 16 +#endif + +#ifdef CURL_DISABLE_VERBOSE_STRINGS +#define ftp_pasv_verbose(a,b,c,d) Curl_nop_stmt +#endif + +/* Local API functions */ +#ifndef DEBUGBUILD +static void _ftp_state(struct Curl_easy *data, + ftpstate newstate); +#define ftp_state(x,y) _ftp_state(x,y) +#else +static void _ftp_state(struct Curl_easy *data, + ftpstate newstate, + int lineno); +#define ftp_state(x,y) _ftp_state(x,y,__LINE__) +#endif + +static CURLcode ftp_sendquote(struct Curl_easy *data, + struct connectdata *conn, + struct curl_slist *quote); +static CURLcode ftp_quit(struct Curl_easy *data, struct connectdata *conn); +static CURLcode ftp_parse_url_path(struct Curl_easy *data); +static CURLcode ftp_regular_transfer(struct Curl_easy *data, bool *done); +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void ftp_pasv_verbose(struct Curl_easy *data, + struct Curl_addrinfo *ai, + char *newhost, /* ascii version */ + int port); +#endif +static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data); +static CURLcode ftp_state_mdtm(struct Curl_easy *data); +static CURLcode ftp_state_quote(struct Curl_easy *data, + bool init, ftpstate instate); +static CURLcode ftp_nb_type(struct Curl_easy *data, + struct connectdata *conn, + bool ascii, ftpstate newstate); +static int ftp_need_type(struct connectdata *conn, + bool ascii); +static CURLcode ftp_do(struct Curl_easy *data, bool *done); +static CURLcode ftp_done(struct Curl_easy *data, + CURLcode, bool premature); +static CURLcode ftp_connect(struct Curl_easy *data, bool *done); +static CURLcode ftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection); +static CURLcode ftp_do_more(struct Curl_easy *data, int *completed); +static CURLcode ftp_multi_statemach(struct Curl_easy *data, bool *done); +static int ftp_getsock(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *socks); +static int ftp_domore_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); +static CURLcode ftp_doing(struct Curl_easy *data, + bool *dophase_done); +static CURLcode ftp_setup_connection(struct Curl_easy *data, + struct connectdata *conn); +static CURLcode init_wc_data(struct Curl_easy *data); +static CURLcode wc_statemach(struct Curl_easy *data); +static void wc_data_dtor(void *ptr); +static CURLcode ftp_state_retr(struct Curl_easy *data, curl_off_t filesize); +static CURLcode ftp_readresp(struct Curl_easy *data, + curl_socket_t sockfd, + struct pingpong *pp, + int *ftpcode, + size_t *size); +static CURLcode ftp_dophase_done(struct Curl_easy *data, + bool connected); + +/* + * FTP protocol handler. + */ + +const struct Curl_handler Curl_handler_ftp = { + "FTP", /* scheme */ + ftp_setup_connection, /* setup_connection */ + ftp_do, /* do_it */ + ftp_done, /* done */ + ftp_do_more, /* do_more */ + ftp_connect, /* connect_it */ + ftp_multi_statemach, /* connecting */ + ftp_doing, /* doing */ + ftp_getsock, /* proto_getsock */ + ftp_getsock, /* doing_getsock */ + ftp_domore_getsock, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ftp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_FTP, /* defport */ + CURLPROTO_FTP, /* protocol */ + CURLPROTO_FTP, /* family */ + PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD | + PROTOPT_NOURLQUERY | PROTOPT_PROXY_AS_HTTP | + PROTOPT_WILDCARD /* flags */ +}; + + +#ifdef USE_SSL +/* + * FTPS protocol handler. + */ + +const struct Curl_handler Curl_handler_ftps = { + "FTPS", /* scheme */ + ftp_setup_connection, /* setup_connection */ + ftp_do, /* do_it */ + ftp_done, /* done */ + ftp_do_more, /* do_more */ + ftp_connect, /* connect_it */ + ftp_multi_statemach, /* connecting */ + ftp_doing, /* doing */ + ftp_getsock, /* proto_getsock */ + ftp_getsock, /* doing_getsock */ + ftp_domore_getsock, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ftp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_FTPS, /* defport */ + CURLPROTO_FTPS, /* protocol */ + CURLPROTO_FTP, /* family */ + PROTOPT_SSL | PROTOPT_DUAL | PROTOPT_CLOSEACTION | + PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY | PROTOPT_WILDCARD /* flags */ +}; +#endif + +static void close_secondarysocket(struct Curl_easy *data, + struct connectdata *conn) +{ + Curl_conn_close(data, SECONDARYSOCKET); + Curl_conn_cf_discard_all(data, conn, SECONDARYSOCKET); +} + +/* + * NOTE: back in the old days, we added code in the FTP code that made NOBODY + * requests on files respond with headers passed to the client/stdout that + * looked like HTTP ones. + * + * This approach is not very elegant, it causes confusion and is error-prone. + * It is subject for removal at the next (or at least a future) soname bump. + * Until then you can test the effects of the removal by undefining the + * following define named CURL_FTP_HTTPSTYLE_HEAD. + */ +#define CURL_FTP_HTTPSTYLE_HEAD 1 + +static void freedirs(struct ftp_conn *ftpc) +{ + if(ftpc->dirs) { + int i; + for(i = 0; i < ftpc->dirdepth; i++) { + free(ftpc->dirs[i]); + ftpc->dirs[i] = NULL; + } + free(ftpc->dirs); + ftpc->dirs = NULL; + ftpc->dirdepth = 0; + } + Curl_safefree(ftpc->file); + + /* no longer of any use */ + Curl_safefree(ftpc->newhost); +} + +/*********************************************************************** + * + * AcceptServerConnect() + * + * After connection request is received from the server this function is + * called to accept the connection and close the listening socket + * + */ +static CURLcode AcceptServerConnect(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + curl_socket_t sock = conn->sock[SECONDARYSOCKET]; + curl_socket_t s = CURL_SOCKET_BAD; +#ifdef ENABLE_IPV6 + struct Curl_sockaddr_storage add; +#else + struct sockaddr_in add; +#endif + curl_socklen_t size = (curl_socklen_t) sizeof(add); + CURLcode result; + + if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) { + size = sizeof(add); + + s = accept(sock, (struct sockaddr *) &add, &size); + } + + if(CURL_SOCKET_BAD == s) { + failf(data, "Error accept()ing server connect"); + return CURLE_FTP_PORT_FAILED; + } + infof(data, "Connection accepted from server"); + /* when this happens within the DO state it is important that we mark us as + not needing DO_MORE anymore */ + conn->bits.do_more = FALSE; + + (void)curlx_nonblock(s, TRUE); /* enable non-blocking */ + /* Replace any filter on SECONDARY with one listening on this socket */ + result = Curl_conn_tcp_accepted_set(data, conn, SECONDARYSOCKET, &s); + if(result) + return result; + + if(data->set.fsockopt) { + int error = 0; + + /* activate callback for setting socket options */ + Curl_set_in_callback(data, true); + error = data->set.fsockopt(data->set.sockopt_client, + s, + CURLSOCKTYPE_ACCEPT); + Curl_set_in_callback(data, false); + + if(error) { + close_secondarysocket(data, conn); + return CURLE_ABORTED_BY_CALLBACK; + } + } + + return CURLE_OK; + +} + +/* + * ftp_timeleft_accept() returns the amount of milliseconds left allowed for + * waiting server to connect. If the value is negative, the timeout time has + * already elapsed. + * + * The start time is stored in progress.t_acceptdata - as set with + * Curl_pgrsTime(..., TIMER_STARTACCEPT); + * + */ +static timediff_t ftp_timeleft_accept(struct Curl_easy *data) +{ + timediff_t timeout_ms = DEFAULT_ACCEPT_TIMEOUT; + timediff_t other; + struct curltime now; + + if(data->set.accepttimeout > 0) + timeout_ms = data->set.accepttimeout; + + now = Curl_now(); + + /* check if the generic timeout possibly is set shorter */ + other = Curl_timeleft(data, &now, FALSE); + if(other && (other < timeout_ms)) + /* note that this also works fine for when other happens to be negative + due to it already having elapsed */ + timeout_ms = other; + else { + /* subtract elapsed time */ + timeout_ms -= Curl_timediff(now, data->progress.t_acceptdata); + if(!timeout_ms) + /* avoid returning 0 as that means no timeout! */ + return -1; + } + + return timeout_ms; +} + + +/*********************************************************************** + * + * ReceivedServerConnect() + * + * After allowing server to connect to us from data port, this function + * checks both data connection for connection establishment and ctrl + * connection for a negative response regarding a failure in connecting + * + */ +static CURLcode ReceivedServerConnect(struct Curl_easy *data, bool *received) +{ + struct connectdata *conn = data->conn; + curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET]; + curl_socket_t data_sock = conn->sock[SECONDARYSOCKET]; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + int result; + timediff_t timeout_ms; + ssize_t nread; + int ftpcode; + + *received = FALSE; + + timeout_ms = ftp_timeleft_accept(data); + infof(data, "Checking for server connect"); + if(timeout_ms < 0) { + /* if a timeout was already reached, bail out */ + failf(data, "Accept timeout occurred while waiting server connect"); + return CURLE_FTP_ACCEPT_TIMEOUT; + } + + /* First check whether there is a cached response from server */ + if(pp->cache_size && pp->cache && pp->cache[0] > '3') { + /* Data connection could not be established, let's return */ + infof(data, "There is negative response in cache while serv connect"); + (void)Curl_GetFTPResponse(data, &nread, &ftpcode); + return CURLE_FTP_ACCEPT_FAILED; + } + + result = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0); + + /* see if the connection request is already here */ + switch(result) { + case -1: /* error */ + /* let's die here */ + failf(data, "Error while waiting for server connect"); + return CURLE_FTP_ACCEPT_FAILED; + case 0: /* Server connect is not received yet */ + break; /* loop */ + default: + + if(result & CURL_CSELECT_IN2) { + infof(data, "Ready to accept data connection from server"); + *received = TRUE; + } + else if(result & CURL_CSELECT_IN) { + infof(data, "Ctrl conn has data while waiting for data conn"); + (void)Curl_GetFTPResponse(data, &nread, &ftpcode); + + if(ftpcode/100 > 3) + return CURLE_FTP_ACCEPT_FAILED; + + return CURLE_WEIRD_SERVER_REPLY; + } + + break; + } /* switch() */ + + return CURLE_OK; +} + + +/*********************************************************************** + * + * InitiateTransfer() + * + * After connection from server is accepted this function is called to + * setup transfer parameters and initiate the data transfer. + * + */ +static CURLcode InitiateTransfer(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + bool connected; + + DEBUGF(infof(data, "ftp InitiateTransfer()")); + if(conn->bits.ftp_use_data_ssl && data->set.ftp_use_port && + !Curl_conn_is_ssl(conn, SECONDARYSOCKET)) { + result = Curl_ssl_cfilter_add(data, conn, SECONDARYSOCKET); + if(result) + return result; + } + result = Curl_conn_connect(data, SECONDARYSOCKET, TRUE, &connected); + if(result || !connected) + return result; + + if(conn->proto.ftpc.state_saved == FTP_STOR) { + /* When we know we're uploading a specified file, we can get the file + size prior to the actual upload. */ + Curl_pgrsSetUploadSize(data, data->state.infilesize); + + /* set the SO_SNDBUF for the secondary socket for those who need it */ + Curl_sndbufset(conn->sock[SECONDARYSOCKET]); + + Curl_setup_transfer(data, -1, -1, FALSE, SECONDARYSOCKET); + } + else { + /* FTP download: */ + Curl_setup_transfer(data, SECONDARYSOCKET, + conn->proto.ftpc.retr_size_saved, FALSE, -1); + } + + conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */ + ftp_state(data, FTP_STOP); + + return CURLE_OK; +} + +/*********************************************************************** + * + * AllowServerConnect() + * + * When we've issue the PORT command, we have told the server to connect to + * us. This function checks whether data connection is established if so it is + * accepted. + * + */ +static CURLcode AllowServerConnect(struct Curl_easy *data, bool *connected) +{ + timediff_t timeout_ms; + CURLcode result = CURLE_OK; + + *connected = FALSE; + infof(data, "Preparing for accepting server on data port"); + + /* Save the time we start accepting server connect */ + Curl_pgrsTime(data, TIMER_STARTACCEPT); + + timeout_ms = ftp_timeleft_accept(data); + if(timeout_ms < 0) { + /* if a timeout was already reached, bail out */ + failf(data, "Accept timeout occurred while waiting server connect"); + result = CURLE_FTP_ACCEPT_TIMEOUT; + goto out; + } + + /* see if the connection request is already here */ + result = ReceivedServerConnect(data, connected); + if(result) + goto out; + + if(*connected) { + result = AcceptServerConnect(data); + if(result) + goto out; + + result = InitiateTransfer(data); + if(result) + goto out; + } + else { + /* Add timeout to multi handle and break out of the loop */ + Curl_expire(data, data->set.accepttimeout ? + data->set.accepttimeout: DEFAULT_ACCEPT_TIMEOUT, + EXPIRE_FTP_ACCEPT); + } + +out: + DEBUGF(infof(data, "ftp AllowServerConnect() -> %d", result)); + return result; +} + +/* macro to check for a three-digit ftp status code at the start of the + given string */ +#define STATUSCODE(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \ + ISDIGIT(line[2])) + +/* macro to check for the last line in an FTP server response */ +#define LASTLINE(line) (STATUSCODE(line) && (' ' == line[3])) + +static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, + char *line, size_t len, int *code) +{ + (void)data; + (void)conn; + + if((len > 3) && LASTLINE(line)) { + *code = curlx_sltosi(strtol(line, NULL, 10)); + return TRUE; + } + + return FALSE; +} + +static CURLcode ftp_readresp(struct Curl_easy *data, + curl_socket_t sockfd, + struct pingpong *pp, + int *ftpcode, /* return the ftp-code if done */ + size_t *size) /* size of the response */ +{ + int code; + CURLcode result = Curl_pp_readresp(data, sockfd, pp, &code, size); + +#ifdef HAVE_GSSAPI + { + struct connectdata *conn = data->conn; + char * const buf = data->state.buffer; + + /* handle the security-oriented responses 6xx ***/ + switch(code) { + case 631: + code = Curl_sec_read_msg(data, conn, buf, PROT_SAFE); + break; + case 632: + code = Curl_sec_read_msg(data, conn, buf, PROT_PRIVATE); + break; + case 633: + code = Curl_sec_read_msg(data, conn, buf, PROT_CONFIDENTIAL); + break; + default: + /* normal ftp stuff we pass through! */ + break; + } + } +#endif + + /* store the latest code for later retrieval */ + data->info.httpcode = code; + + if(ftpcode) + *ftpcode = code; + + if(421 == code) { + /* 421 means "Service not available, closing control connection." and FTP + * servers use it to signal that idle session timeout has been exceeded. + * If we ignored the response, it could end up hanging in some cases. + * + * This response code can come at any point so having it treated + * generically is a good idea. + */ + infof(data, "We got a 421 - timeout"); + ftp_state(data, FTP_STOP); + return CURLE_OPERATION_TIMEDOUT; + } + + return result; +} + +/* --- parse FTP server responses --- */ + +/* + * Curl_GetFTPResponse() is a BLOCKING function to read the full response + * from a server after a command. + * + */ + +CURLcode Curl_GetFTPResponse(struct Curl_easy *data, + ssize_t *nreadp, /* return number of bytes read */ + int *ftpcode) /* return the ftp-code */ +{ + /* + * We cannot read just one byte per read() and then go back to select() as + * the OpenSSL read() doesn't grok that properly. + * + * Alas, read as much as possible, split up into lines, use the ending + * line in a response or continue reading. */ + + struct connectdata *conn = data->conn; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + size_t nread; + int cache_skip = 0; + int value_to_be_ignored = 0; + + if(ftpcode) + *ftpcode = 0; /* 0 for errors */ + else + /* make the pointer point to something for the rest of this function */ + ftpcode = &value_to_be_ignored; + + *nreadp = 0; + + while(!*ftpcode && !result) { + /* check and reset timeout value every lap */ + timediff_t timeout = Curl_pp_state_timeout(data, pp, FALSE); + timediff_t interval_ms; + + if(timeout <= 0) { + failf(data, "FTP response timeout"); + return CURLE_OPERATION_TIMEDOUT; /* already too little time */ + } + + interval_ms = 1000; /* use 1 second timeout intervals */ + if(timeout < interval_ms) + interval_ms = timeout; + + /* + * Since this function is blocking, we need to wait here for input on the + * connection and only then we call the response reading function. We do + * timeout at least every second to make the timeout check run. + * + * A caution here is that the ftp_readresp() function has a cache that may + * contain pieces of a response from the previous invoke and we need to + * make sure we don't just wait for input while there is unhandled data in + * that cache. But also, if the cache is there, we call ftp_readresp() and + * the cache wasn't good enough to continue we must not just busy-loop + * around this function. + * + */ + + if(pp->cache && (cache_skip < 2)) { + /* + * There's a cache left since before. We then skipping the wait for + * socket action, unless this is the same cache like the previous round + * as then the cache was deemed not enough to act on and we then need to + * wait for more data anyway. + */ + } + else if(!Curl_conn_data_pending(data, FIRSTSOCKET)) { + switch(SOCKET_READABLE(sockfd, interval_ms)) { + case -1: /* select() error, stop reading */ + failf(data, "FTP response aborted due to select/poll error: %d", + SOCKERRNO); + return CURLE_RECV_ERROR; + + case 0: /* timeout */ + if(Curl_pgrsUpdate(data)) + return CURLE_ABORTED_BY_CALLBACK; + continue; /* just continue in our loop for the timeout duration */ + + default: /* for clarity */ + break; + } + } + result = ftp_readresp(data, sockfd, pp, ftpcode, &nread); + if(result) + break; + + if(!nread && pp->cache) + /* bump cache skip counter as on repeated skips we must wait for more + data */ + cache_skip++; + else + /* when we got data or there is no cache left, we reset the cache skip + counter */ + cache_skip = 0; + + *nreadp += nread; + + } /* while there's buffer left and loop is requested */ + + pp->pending_resp = FALSE; + + return result; +} + +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ +static const char * const ftp_state_names[]={ + "STOP", + "WAIT220", + "AUTH", + "USER", + "PASS", + "ACCT", + "PBSZ", + "PROT", + "CCC", + "PWD", + "SYST", + "NAMEFMT", + "QUOTE", + "RETR_PREQUOTE", + "STOR_PREQUOTE", + "POSTQUOTE", + "CWD", + "MKD", + "MDTM", + "TYPE", + "LIST_TYPE", + "RETR_TYPE", + "STOR_TYPE", + "SIZE", + "RETR_SIZE", + "STOR_SIZE", + "REST", + "RETR_REST", + "PORT", + "PRET", + "PASV", + "LIST", + "RETR", + "STOR", + "QUIT" +}; +#endif + +/* This is the ONLY way to change FTP state! */ +static void _ftp_state(struct Curl_easy *data, + ftpstate newstate +#ifdef DEBUGBUILD + , int lineno +#endif + ) +{ + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + +#if defined(DEBUGBUILD) + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) lineno; +#else + if(ftpc->state != newstate) + infof(data, "FTP %p (line %d) state change from %s to %s", + (void *)ftpc, lineno, ftp_state_names[ftpc->state], + ftp_state_names[newstate]); +#endif +#endif + + ftpc->state = newstate; +} + +static CURLcode ftp_state_user(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = Curl_pp_sendf(data, + &conn->proto.ftpc.pp, "USER %s", + conn->user?conn->user:""); + if(!result) { + struct ftp_conn *ftpc = &conn->proto.ftpc; + ftpc->ftp_trying_alternative = FALSE; + ftp_state(data, FTP_USER); + } + return result; +} + +static CURLcode ftp_state_pwd(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "PWD"); + if(!result) + ftp_state(data, FTP_PWD); + + return result; +} + +/* For the FTP "protocol connect" and "doing" phases only */ +static int ftp_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) +{ + return Curl_pp_getsock(data, &conn->proto.ftpc.pp, socks); +} + +/* For the FTP "DO_MORE" phase only */ +static int ftp_domore_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + (void)data; + + /* When in DO_MORE state, we could be either waiting for us to connect to a + * remote site, or we could wait for that site to connect to us. Or just + * handle ordinary commands. + */ + + DEBUGF(infof(data, "ftp_domore_getsock()")); + if(conn->cfilter[SECONDARYSOCKET] + && !Curl_conn_is_connected(conn, SECONDARYSOCKET)) + return 0; + + if(FTP_STOP == ftpc->state) { + int bits = GETSOCK_READSOCK(0); + + /* if stopped and still in this state, then we're also waiting for a + connect on the secondary connection */ + socks[0] = conn->sock[FIRSTSOCKET]; + if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { + socks[1] = conn->sock[SECONDARYSOCKET]; + bits |= GETSOCK_WRITESOCK(1) | GETSOCK_READSOCK(1); + } + + return bits; + } + return Curl_pp_getsock(data, &conn->proto.ftpc.pp, socks); +} + +/* This is called after the FTP_QUOTE state is passed. + + 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. +*/ +static CURLcode ftp_state_cwd(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if(ftpc->cwddone) + /* already done and fine */ + result = ftp_state_mdtm(data); + else { + /* FTPFILE_NOCWD with full path: expect ftpc->cwddone! */ + DEBUGASSERT((data->set.ftp_filemethod != FTPFILE_NOCWD) || + !(ftpc->dirdepth && ftpc->dirs[0][0] == '/')); + + ftpc->count2 = 0; /* count2 counts failed CWDs */ + + if(conn->bits.reuse && ftpc->entrypath && + /* no need to go to entrypath when we have an absolute path */ + !(ftpc->dirdepth && ftpc->dirs[0][0] == '/')) { + /* This is a reused connection. Since we change directory to where the + transfer is taking place, we must first get back to the original dir + where we ended up after login: */ + ftpc->cwdcount = 0; /* we count this as the first path, then we add one + for all upcoming ones in the ftp->dirs[] array */ + result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", ftpc->entrypath); + if(!result) + ftp_state(data, FTP_CWD); + } + else { + if(ftpc->dirdepth) { + ftpc->cwdcount = 1; + /* issue the first CWD, the rest is sent when the CWD responses are + received... */ + result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", + ftpc->dirs[ftpc->cwdcount -1]); + if(!result) + ftp_state(data, FTP_CWD); + } + else { + /* No CWD necessary */ + result = ftp_state_mdtm(data); + } + } + } + return result; +} + +typedef enum { + EPRT, + PORT, + DONE +} ftpport; + +static CURLcode ftp_state_use_port(struct Curl_easy *data, + ftpport fcmd) /* start with this */ +{ + CURLcode result = CURLE_FTP_PORT_FAILED; + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + curl_socket_t portsock = CURL_SOCKET_BAD; + char myhost[MAX_IPADR_LEN + 1] = ""; + + struct Curl_sockaddr_storage ss; + struct Curl_addrinfo *res, *ai; + curl_socklen_t sslen; + char hbuf[NI_MAXHOST]; + struct sockaddr *sa = (struct sockaddr *)&ss; + struct sockaddr_in * const sa4 = (void *)sa; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 * const sa6 = (void *)sa; +#endif + static const char mode[][5] = { "EPRT", "PORT" }; + enum resolve_t rc; + int error; + char *host = NULL; + char *string_ftpport = data->set.str[STRING_FTPPORT]; + struct Curl_dns_entry *h = NULL; + unsigned short port_min = 0; + unsigned short port_max = 0; + unsigned short port; + bool possibly_non_local = TRUE; + char buffer[STRERROR_LEN]; + char *addr = NULL; + + /* Step 1, figure out what is requested, + * accepted format : + * (ipv4|ipv6|domain|interface)?(:port(-range)?)? + */ + + if(data->set.str[STRING_FTPPORT] && + (strlen(data->set.str[STRING_FTPPORT]) > 1)) { + +#ifdef ENABLE_IPV6 + size_t addrlen = INET6_ADDRSTRLEN > strlen(string_ftpport) ? + INET6_ADDRSTRLEN : strlen(string_ftpport); +#else + size_t addrlen = INET_ADDRSTRLEN > strlen(string_ftpport) ? + INET_ADDRSTRLEN : strlen(string_ftpport); +#endif + char *ip_start = string_ftpport; + char *ip_end = NULL; + char *port_start = NULL; + char *port_sep = NULL; + + addr = calloc(1, addrlen + 1); + if(!addr) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + +#ifdef ENABLE_IPV6 + if(*string_ftpport == '[') { + /* [ipv6]:port(-range) */ + ip_start = string_ftpport + 1; + ip_end = strchr(string_ftpport, ']'); + if(ip_end) + strncpy(addr, ip_start, ip_end - ip_start); + } + else +#endif + if(*string_ftpport == ':') { + /* :port */ + ip_end = string_ftpport; + } + else { + ip_end = strchr(string_ftpport, ':'); + if(ip_end) { + /* either ipv6 or (ipv4|domain|interface):port(-range) */ +#ifdef ENABLE_IPV6 + if(Curl_inet_pton(AF_INET6, string_ftpport, &sa6->sin6_addr) == 1) { + /* ipv6 */ + port_min = port_max = 0; + strcpy(addr, string_ftpport); + ip_end = NULL; /* this got no port ! */ + } + else +#endif + /* (ipv4|domain|interface):port(-range) */ + strncpy(addr, string_ftpport, ip_end - ip_start); + } + else + /* ipv4|interface */ + strcpy(addr, string_ftpport); + } + + /* parse the port */ + if(ip_end) { + port_start = strchr(ip_end, ':'); + if(port_start) { + port_min = curlx_ultous(strtoul(port_start + 1, NULL, 10)); + port_sep = strchr(port_start, '-'); + if(port_sep) { + port_max = curlx_ultous(strtoul(port_sep + 1, NULL, 10)); + } + else + port_max = port_min; + } + } + + /* correct errors like: + * :1234-1230 + * :-4711, in this case port_min is (unsigned)-1, + * therefore port_min > port_max for all cases + * but port_max = (unsigned)-1 + */ + if(port_min > port_max) + port_min = port_max = 0; + + if(*addr != '\0') { + /* attempt to get the address of the given interface name */ + switch(Curl_if2ip(conn->remote_addr->family, +#ifdef ENABLE_IPV6 + Curl_ipv6_scope(&conn->remote_addr->sa_addr), + conn->scope_id, +#endif + addr, hbuf, sizeof(hbuf))) { + case IF2IP_NOT_FOUND: + /* not an interface, use the given string as host name instead */ + host = addr; + break; + case IF2IP_AF_NOT_SUPPORTED: + goto out; + case IF2IP_FOUND: + host = hbuf; /* use the hbuf for host name */ + } + } + else + /* there was only a port(-range) given, default the host */ + host = NULL; + } /* data->set.ftpport */ + + if(!host) { + const char *r; + /* not an interface and not a host name, get default by extracting + the IP from the control connection */ + sslen = sizeof(ss); + if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + goto out; + } + switch(sa->sa_family) { +#ifdef ENABLE_IPV6 + case AF_INET6: + r = Curl_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, sizeof(hbuf)); + break; +#endif + default: + r = Curl_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, sizeof(hbuf)); + break; + } + if(!r) { + goto out; + } + host = hbuf; /* use this host name */ + possibly_non_local = FALSE; /* we know it is local now */ + } + + /* resolv ip/host to ip */ + rc = Curl_resolv(data, host, 0, FALSE, &h); + if(rc == CURLRESOLV_PENDING) + (void)Curl_resolver_wait_resolv(data, &h); + if(h) { + res = h->addr; + /* when we return from this function, we can forget about this entry + to we can unlock it now already */ + Curl_resolv_unlock(data, h); + } /* (h) */ + else + res = NULL; /* failure! */ + + if(!res) { + failf(data, "failed to resolve the address provided to PORT: %s", host); + goto out; + } + + host = NULL; + + /* step 2, create a socket for the requested address */ + error = 0; + for(ai = res; ai; ai = ai->ai_next) { + if(Curl_socket_open(data, ai, NULL, conn->transport, &portsock)) { + error = SOCKERRNO; + continue; + } + break; + } + if(!ai) { + failf(data, "socket failure: %s", + Curl_strerror(error, buffer, sizeof(buffer))); + goto out; + } + DEBUGF(infof(data, "ftp_state_use_port(), opened socket")); + + /* step 3, bind to a suitable local address */ + + memcpy(sa, ai->ai_addr, ai->ai_addrlen); + sslen = ai->ai_addrlen; + + for(port = port_min; port <= port_max;) { + if(sa->sa_family == AF_INET) + sa4->sin_port = htons(port); +#ifdef ENABLE_IPV6 + else + sa6->sin6_port = htons(port); +#endif + /* Try binding the given address. */ + if(bind(portsock, sa, sslen) ) { + /* It failed. */ + error = SOCKERRNO; + if(possibly_non_local && (error == EADDRNOTAVAIL)) { + /* The requested bind address is not local. Use the address used for + * the control connection instead and restart the port loop + */ + infof(data, "bind(port=%hu) on non-local address failed: %s", port, + Curl_strerror(error, buffer, sizeof(buffer))); + + sslen = sizeof(ss); + if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + goto out; + } + port = port_min; + possibly_non_local = FALSE; /* don't try this again */ + continue; + } + if(error != EADDRINUSE && error != EACCES) { + failf(data, "bind(port=%hu) failed: %s", port, + Curl_strerror(error, buffer, sizeof(buffer))); + goto out; + } + } + else + break; + + port++; + } + + /* maybe all ports were in use already */ + if(port > port_max) { + failf(data, "bind() failed, we ran out of ports"); + goto out; + } + + /* get the name again after the bind() so that we can extract the + port number it uses now */ + sslen = sizeof(ss); + if(getsockname(portsock, sa, &sslen)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + goto out; + } + DEBUGF(infof(data, "ftp_state_use_port(), socket bound to port %d", port)); + + /* step 4, listen on the socket */ + + if(listen(portsock, 1)) { + failf(data, "socket failure: %s", + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + goto out; + } + DEBUGF(infof(data, "ftp_state_use_port(), listening on %d", port)); + + /* step 5, send the proper FTP command */ + + /* get a plain printable version of the numerical address to work with + below */ + Curl_printable_address(ai, myhost, sizeof(myhost)); + +#ifdef ENABLE_IPV6 + if(!conn->bits.ftp_use_eprt && conn->bits.ipv6) + /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the + request and enable EPRT again! */ + conn->bits.ftp_use_eprt = TRUE; +#endif + + for(; fcmd != DONE; fcmd++) { + + if(!conn->bits.ftp_use_eprt && (EPRT == fcmd)) + /* if disabled, goto next */ + continue; + + if((PORT == fcmd) && sa->sa_family != AF_INET) + /* PORT is IPv4 only */ + continue; + + switch(sa->sa_family) { + case AF_INET: + port = ntohs(sa4->sin_port); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + port = ntohs(sa6->sin6_port); + break; +#endif + default: + continue; /* might as well skip this */ + } + + if(EPRT == fcmd) { + /* + * Two fine examples from RFC2428; + * + * EPRT |1|132.235.1.2|6275| + * + * EPRT |2|1080::8:800:200C:417A|5282| + */ + + result = Curl_pp_sendf(data, &ftpc->pp, "%s |%d|%s|%hu|", mode[fcmd], + sa->sa_family == AF_INET?1:2, + myhost, port); + if(result) { + failf(data, "Failure sending EPRT command: %s", + curl_easy_strerror(result)); + goto out; + } + break; + } + if(PORT == fcmd) { + /* large enough for [IP address],[num],[num] */ + char target[sizeof(myhost) + 20]; + char *source = myhost; + char *dest = target; + + /* translate x.x.x.x to x,x,x,x */ + while(source && *source) { + if(*source == '.') + *dest = ','; + else + *dest = *source; + dest++; + source++; + } + *dest = 0; + msnprintf(dest, 20, ",%d,%d", (int)(port>>8), (int)(port&0xff)); + + result = Curl_pp_sendf(data, &ftpc->pp, "%s %s", mode[fcmd], target); + if(result) { + failf(data, "Failure sending PORT command: %s", + curl_easy_strerror(result)); + goto out; + } + break; + } + } + + /* store which command was sent */ + ftpc->count1 = fcmd; + + /* Replace any filter on SECONDARY with one listening on this socket */ + result = Curl_conn_tcp_listen_set(data, conn, SECONDARYSOCKET, &portsock); + if(result) + goto out; + portsock = CURL_SOCKET_BAD; /* now held in filter */ + ftp_state(data, FTP_PORT); + +out: + if(result) { + ftp_state(data, FTP_STOP); + } + if(portsock != CURL_SOCKET_BAD) + Curl_socket_close(data, conn, portsock); + free(addr); + return result; +} + +static CURLcode ftp_state_use_pasv(struct Curl_easy *data, + struct connectdata *conn) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + CURLcode result = CURLE_OK; + /* + Here's the executive summary on what to do: + + 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) + + EPSV is RFC2428, expect: + 229 Entering Extended Passive Mode (|||port|) + + */ + + static const char mode[][5] = { "EPSV", "PASV" }; + int modeoff; + +#ifdef PF_INET6 + if(!conn->bits.ftp_use_epsv && conn->bits.ipv6) + /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the + request and enable EPSV again! */ + conn->bits.ftp_use_epsv = TRUE; +#endif + + modeoff = conn->bits.ftp_use_epsv?0:1; + + result = Curl_pp_sendf(data, &ftpc->pp, "%s", mode[modeoff]); + if(!result) { + ftpc->count1 = modeoff; + ftp_state(data, FTP_PASV); + infof(data, "Connect data stream passively"); + } + return result; +} + +/* + * ftp_state_prepare_transfer() starts PORT, PASV or PRET etc. + * + * REST is the last command in the chain of commands when a "head"-like + * request is made. Thus, if an actual transfer is to be made this is where we + * take off for real. + */ +static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; + + if(ftp->transfer != PPTRANSFER_BODY) { + /* doesn't transfer any data */ + + /* still possibly do PRE QUOTE jobs */ + ftp_state(data, FTP_RETR_PREQUOTE); + result = ftp_state_quote(data, TRUE, FTP_RETR_PREQUOTE); + } + else if(data->set.ftp_use_port) { + /* We have chosen to use the PORT (or similar) command */ + result = ftp_state_use_port(data, EPRT); + } + else { + /* We have chosen (this is default) to use the PASV (or similar) command */ + if(data->set.ftp_use_pret) { + /* The user has requested that we send a PRET command + to prepare the server for the upcoming PASV */ + struct ftp_conn *ftpc = &conn->proto.ftpc; + if(!conn->proto.ftpc.file) + result = Curl_pp_sendf(data, &ftpc->pp, "PRET %s", + data->set.str[STRING_CUSTOMREQUEST]? + data->set.str[STRING_CUSTOMREQUEST]: + (data->state.list_only?"NLST":"LIST")); + else if(data->state.upload) + result = Curl_pp_sendf(data, &ftpc->pp, "PRET STOR %s", + conn->proto.ftpc.file); + else + result = Curl_pp_sendf(data, &ftpc->pp, "PRET RETR %s", + conn->proto.ftpc.file); + if(!result) + ftp_state(data, FTP_PRET); + } + else + result = ftp_state_use_pasv(data, conn); + } + return result; +} + +static CURLcode ftp_state_rest(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = data->req.p.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if((ftp->transfer != PPTRANSFER_BODY) && ftpc->file) { + /* if a "head"-like request is being made (on a file) */ + + /* Determine if server can respond to REST command and therefore + whether it supports range */ + result = Curl_pp_sendf(data, &ftpc->pp, "REST %d", 0); + if(!result) + ftp_state(data, FTP_REST); + } + else + result = ftp_state_prepare_transfer(data); + + return result; +} + +static CURLcode ftp_state_size(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = data->req.p.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if((ftp->transfer == PPTRANSFER_INFO) && ftpc->file) { + /* if a "head"-like request is being made (on a file) */ + + /* we know ftpc->file is a valid pointer to a file name */ + result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); + if(!result) + ftp_state(data, FTP_SIZE); + } + else + result = ftp_state_rest(data, conn); + + return result; +} + +static CURLcode ftp_state_list(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; + + /* If this output is to be machine-parsed, the NLST command might be better + to use, since the LIST command output is not specified or standard in any + 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 + 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 just do LIST (in that case: nothing to do here) + */ + char *lstArg = NULL; + char *cmd; + + if((data->set.ftp_filemethod == FTPFILE_NOCWD) && ftp->path) { + /* url-decode before evaluation: e.g. paths starting/ending with %2f */ + const char *slashPos = NULL; + char *rawPath = NULL; + result = Curl_urldecode(ftp->path, 0, &rawPath, NULL, REJECT_CTRL); + if(result) + return result; + + slashPos = strrchr(rawPath, '/'); + if(slashPos) { + /* chop off the file part if format is dir/file otherwise remove + the trailing slash for dir/dir/ except for absolute path / */ + size_t n = slashPos - rawPath; + if(n == 0) + ++n; + + lstArg = rawPath; + lstArg[n] = '\0'; + } + else + free(rawPath); + } + + cmd = aprintf("%s%s%s", + data->set.str[STRING_CUSTOMREQUEST]? + data->set.str[STRING_CUSTOMREQUEST]: + (data->state.list_only?"NLST":"LIST"), + lstArg? " ": "", + lstArg? lstArg: ""); + free(lstArg); + + if(!cmd) + return CURLE_OUT_OF_MEMORY; + + result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", cmd); + free(cmd); + + if(!result) + ftp_state(data, FTP_LIST); + + return result; +} + +static CURLcode ftp_state_retr_prequote(struct Curl_easy *data) +{ + /* We've sent the TYPE, now we must send the list of prequote strings */ + return ftp_state_quote(data, TRUE, FTP_RETR_PREQUOTE); +} + +static CURLcode ftp_state_stor_prequote(struct Curl_easy *data) +{ + /* We've sent the TYPE, now we must send the list of prequote strings */ + return ftp_state_quote(data, TRUE, FTP_STOR_PREQUOTE); +} + +static CURLcode ftp_state_type(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + /* If we have selected NOBODY and HEADER, it means that we only want file + information. Which in FTP can't be much more than the file size and + date. */ + if(data->req.no_body && ftpc->file && + ftp_need_type(conn, data->state.prefer_ascii)) { + /* The SIZE command is _not_ RFC 959 specified, and therefore many servers + may not support it! It is however the only way we have to get a file's + size! */ + + ftp->transfer = PPTRANSFER_INFO; + /* this means no actual transfer will be made */ + + /* Some servers return different sizes for different modes, and thus we + must set the proper type before we check the size */ + result = ftp_nb_type(data, conn, data->state.prefer_ascii, FTP_TYPE); + if(result) + return result; + } + else + result = ftp_state_size(data, conn); + + return result; +} + +/* This is called after the CWD commands have been done in the beginning of + the DO phase */ +static CURLcode ftp_state_mdtm(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + /* Requested time of file or time-depended transfer? */ + if((data->set.get_filetime || data->set.timecondition) && ftpc->file) { + + /* we have requested to get the modified-time of the file, this is a white + spot as the MDTM is not mentioned in RFC959 */ + result = Curl_pp_sendf(data, &ftpc->pp, "MDTM %s", ftpc->file); + + if(!result) + ftp_state(data, FTP_MDTM); + } + else + result = ftp_state_type(data); + + return result; +} + + +/* This is called after the TYPE and possible quote commands have been sent */ +static CURLcode ftp_state_ul_setup(struct Curl_easy *data, + bool sizechecked) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct FTP *ftp = data->req.p.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + bool append = data->set.remote_append; + + if((data->state.resume_from && !sizechecked) || + ((data->state.resume_from > 0) && sizechecked)) { + /* we're about to continue the uploading of a file */ + /* 1. get already existing file's size. We use the SIZE command for this + which may not exist in the server! The SIZE command is not in + RFC959. */ + + /* 2. This used to set REST. But since we can do append, we + don't another ftp command. We just skip the source file + offset and then we APPEND the rest on the file instead */ + + /* 3. pass file-size number of bytes in the source file */ + /* 4. lower the infilesize counter */ + /* => transfer as usual */ + int seekerr = CURL_SEEKFUNC_OK; + + if(data->state.resume_from < 0) { + /* Got no given size to start from, figure it out */ + result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); + if(!result) + ftp_state(data, FTP_STOR_SIZE); + return result; + } + + /* enable append */ + append = TRUE; + + /* Let's read off the proper amount of bytes from the input. */ + if(conn->seek_func) { + Curl_set_in_callback(data, true); + seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, + SEEK_SET); + Curl_set_in_callback(data, false); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_FTP_COULDNT_USE_REST; + } + /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + do { + size_t readthisamountnow = + (data->state.resume_from - passed > data->set.buffer_size) ? + (size_t)data->set.buffer_size : + curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread = + data->state.fread_func(data->state.buffer, 1, readthisamountnow, + data->state.in); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Failed to read data"); + return CURLE_FTP_COULDNT_USE_REST; + } + } while(passed < data->state.resume_from); + } + /* now, decrease the size of the read */ + if(data->state.infilesize>0) { + data->state.infilesize -= data->state.resume_from; + + if(data->state.infilesize <= 0) { + infof(data, "File already completely uploaded"); + + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + + /* Set ->transfer so that we won't get any error in + * ftp_done() because we didn't transfer anything! */ + ftp->transfer = PPTRANSFER_NONE; + + ftp_state(data, FTP_STOP); + return CURLE_OK; + } + } + /* we've passed, proceed as normal */ + } /* resume_from */ + + result = Curl_pp_sendf(data, &ftpc->pp, append?"APPE %s":"STOR %s", + ftpc->file); + if(!result) + ftp_state(data, FTP_STOR); + + return result; +} + +static CURLcode ftp_state_quote(struct Curl_easy *data, + bool init, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + bool quote = FALSE; + struct curl_slist *item; + + switch(instate) { + case FTP_QUOTE: + default: + item = data->set.quote; + break; + case FTP_RETR_PREQUOTE: + case FTP_STOR_PREQUOTE: + item = data->set.prequote; + break; + case FTP_POSTQUOTE: + item = data->set.postquote; + break; + } + + /* + * This state uses: + * 'count1' to iterate over the commands to send + * 'count2' to store whether to allow commands to fail + */ + + if(init) + ftpc->count1 = 0; + else + ftpc->count1++; + + if(item) { + int i = 0; + + /* Skip count1 items in the linked list */ + while((i< ftpc->count1) && item) { + item = item->next; + i++; + } + if(item) { + char *cmd = item->data; + if(cmd[0] == '*') { + cmd++; + ftpc->count2 = 1; /* the sent command is allowed to fail */ + } + else + ftpc->count2 = 0; /* failure means cancel operation */ + + result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); + if(result) + return result; + ftp_state(data, instate); + quote = TRUE; + } + } + + if(!quote) { + /* No more quote to send, continue to ... */ + switch(instate) { + case FTP_QUOTE: + default: + result = ftp_state_cwd(data, conn); + break; + case FTP_RETR_PREQUOTE: + if(ftp->transfer != PPTRANSFER_BODY) + ftp_state(data, FTP_STOP); + else { + if(ftpc->known_filesize != -1) { + Curl_pgrsSetDownloadSize(data, ftpc->known_filesize); + result = ftp_state_retr(data, ftpc->known_filesize); + } + else { + if(data->set.ignorecl || data->state.prefer_ascii) { + /* 'ignorecl' is used to support download of growing files. It + prevents the state machine from requesting the file size from + the server. With an unknown file size the download continues + until the server terminates it, otherwise the client stops if + the received byte count exceeds the reported file size. Set + option CURLOPT_IGNORE_CONTENT_LENGTH to 1 to enable this + behavior. + + In addition: asking for the size for 'TYPE A' transfers is not + constructive since servers don't report the converted size. So + skip it. + */ + result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); + if(!result) + ftp_state(data, FTP_RETR); + } + else { + result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); + if(!result) + ftp_state(data, FTP_RETR_SIZE); + } + } + } + break; + case FTP_STOR_PREQUOTE: + result = ftp_state_ul_setup(data, FALSE); + break; + case FTP_POSTQUOTE: + break; + } + } + + return result; +} + +/* called from ftp_state_pasv_resp to switch to PASV in case of EPSV + problems */ +static CURLcode ftp_epsv_disable(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + if(conn->bits.ipv6 +#ifndef CURL_DISABLE_PROXY + && !(conn->bits.tunnel_proxy || conn->bits.socksproxy) +#endif + ) { + /* We can't disable EPSV when doing IPv6, so this is instead a fail */ + failf(data, "Failed EPSV attempt, exiting"); + return CURLE_WEIRD_SERVER_REPLY; + } + + infof(data, "Failed EPSV attempt. Disabling EPSV"); + /* disable it for next transfer */ + conn->bits.ftp_use_epsv = FALSE; + Curl_conn_close(data, SECONDARYSOCKET); + Curl_conn_cf_discard_all(data, conn, SECONDARYSOCKET); + data->state.errorbuf = FALSE; /* allow error message to get + rewritten */ + result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "PASV"); + if(!result) { + conn->proto.ftpc.count1++; + /* remain in/go to the FTP_PASV state */ + ftp_state(data, FTP_PASV); + } + return result; +} + + +static char *control_address(struct connectdata *conn) +{ + /* Returns the control connection IP address. + If a proxy tunnel is used, returns the original host name instead, because + the effective control connection address is the proxy address, + not the ftp host. */ +#ifndef CURL_DISABLE_PROXY + if(conn->bits.tunnel_proxy || conn->bits.socksproxy) + return conn->host.name; +#endif + return conn->primary_ip; +} + +static bool match_pasv_6nums(const char *p, + unsigned int *array) /* 6 numbers */ +{ + int i; + for(i = 0; i < 6; i++) { + unsigned long num; + char *endp; + if(i) { + if(*p != ',') + return FALSE; + p++; + } + if(!ISDIGIT(*p)) + return FALSE; + num = strtoul(p, &endp, 10); + if(num > 255) + return FALSE; + array[i] = (unsigned int)num; + p = endp; + } + return TRUE; +} + +static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, + int ftpcode) +{ + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + CURLcode result; + struct Curl_dns_entry *addr = NULL; + enum resolve_t rc; + unsigned short connectport; /* the local port connect() should use! */ + char *str = &data->state.buffer[4]; /* start on the first letter */ + + /* if we come here again, make sure the former name is cleared */ + Curl_safefree(ftpc->newhost); + + if((ftpc->count1 == 0) && + (ftpcode == 229)) { + /* positive EPSV response */ + char *ptr = strchr(str, '('); + if(ptr) { + char sep; + ptr++; + /* |||12345| */ + sep = ptr[0]; + /* the ISDIGIT() check here is because strtoul() accepts leading minus + etc */ + if((ptr[1] == sep) && (ptr[2] == sep) && ISDIGIT(ptr[3])) { + char *endp; + unsigned long num = strtoul(&ptr[3], &endp, 10); + if(*endp != sep) + ptr = NULL; + else if(num > 0xffff) { + failf(data, "Illegal port number in EPSV reply"); + return CURLE_FTP_WEIRD_PASV_REPLY; + } + if(ptr) { + ftpc->newport = (unsigned short)(num & 0xffff); + ftpc->newhost = strdup(control_address(conn)); + if(!ftpc->newhost) + return CURLE_OUT_OF_MEMORY; + } + } + else + ptr = NULL; + } + if(!ptr) { + failf(data, "Weirdly formatted EPSV reply"); + return CURLE_FTP_WEIRD_PASV_REPLY; + } + } + else if((ftpc->count1 == 1) && + (ftpcode == 227)) { + /* positive PASV response */ + unsigned int ip[6]; + + /* + * Scan for a sequence of six comma-separated numbers and use them as + * IP+port indicators. + * + * Found reply-strings include: + * "227 Entering Passive Mode (127,0,0,1,4,51)" + * "227 Data transfer will passively listen to 127,0,0,1,4,51" + * "227 Entering passive mode. 127,0,0,1,4,51" + */ + while(*str) { + if(match_pasv_6nums(str, ip)) + break; + str++; + } + + if(!*str) { + failf(data, "Couldn't interpret the 227-response"); + return CURLE_FTP_WEIRD_227_FORMAT; + } + + /* we got OK from server */ + if(data->set.ftp_skip_ip) { + /* told to ignore the remotely given IP but instead use the host we used + for the control connection */ + infof(data, "Skip %u.%u.%u.%u for data connection, reuse %s instead", + ip[0], ip[1], ip[2], ip[3], + conn->host.name); + ftpc->newhost = strdup(control_address(conn)); + } + else + ftpc->newhost = aprintf("%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]); + + if(!ftpc->newhost) + return CURLE_OUT_OF_MEMORY; + + ftpc->newport = (unsigned short)(((ip[4]<<8) + ip[5]) & 0xffff); + } + else if(ftpc->count1 == 0) { + /* EPSV failed, move on to PASV */ + return ftp_epsv_disable(data, conn); + } + else { + failf(data, "Bad PASV/EPSV response: %03d", ftpcode); + return CURLE_FTP_WEIRD_PASV_REPLY; + } + +#ifndef CURL_DISABLE_PROXY + if(conn->bits.proxy) { + /* + * This connection uses a proxy and we need to connect to the proxy again + * here. We don't want to rely on a former host lookup that might've + * expired now, instead we remake the lookup here and now! + */ + const char * const host_name = conn->bits.socksproxy ? + conn->socks_proxy.host.name : conn->http_proxy.host.name; + rc = Curl_resolv(data, host_name, conn->port, FALSE, &addr); + if(rc == CURLRESOLV_PENDING) + /* BLOCKING, ignores the return code but 'addr' will be NULL in + case of failure */ + (void)Curl_resolver_wait_resolv(data, &addr); + + connectport = + (unsigned short)conn->port; /* we connect to the proxy's port */ + + if(!addr) { + failf(data, "Can't resolve proxy host %s:%hu", host_name, connectport); + return CURLE_COULDNT_RESOLVE_PROXY; + } + } + else +#endif + { + /* normal, direct, ftp connection */ + DEBUGASSERT(ftpc->newhost); + + /* postponed address resolution in case of tcp fastopen */ + if(conn->bits.tcp_fastopen && !conn->bits.reuse && !ftpc->newhost[0]) { + Curl_conn_ev_update_info(data, conn); + Curl_safefree(ftpc->newhost); + ftpc->newhost = strdup(control_address(conn)); + if(!ftpc->newhost) + return CURLE_OUT_OF_MEMORY; + } + + rc = Curl_resolv(data, ftpc->newhost, ftpc->newport, FALSE, &addr); + if(rc == CURLRESOLV_PENDING) + /* BLOCKING */ + (void)Curl_resolver_wait_resolv(data, &addr); + + connectport = ftpc->newport; /* we connect to the remote port */ + + if(!addr) { + failf(data, "Can't resolve new host %s:%hu", ftpc->newhost, connectport); + return CURLE_FTP_CANT_GET_HOST; + } + } + + result = Curl_conn_setup(data, conn, SECONDARYSOCKET, addr, + conn->bits.ftp_use_data_ssl? + CURL_CF_SSL_ENABLE : CURL_CF_SSL_DISABLE); + + if(result) { + Curl_resolv_unlock(data, addr); /* we're done using this address */ + if(ftpc->count1 == 0 && ftpcode == 229) + return ftp_epsv_disable(data, conn); + + return result; + } + + + /* + * When this is used from the multi interface, this might've returned with + * the 'connected' set to FALSE and thus we are now awaiting a non-blocking + * connect to connect. + */ + + if(data->set.verbose) + /* this just dumps information about this second connection */ + ftp_pasv_verbose(data, addr->addr, ftpc->newhost, connectport); + + Curl_resolv_unlock(data, addr); /* we're done using this address */ + + Curl_safefree(conn->secondaryhostname); + conn->secondary_port = ftpc->newport; + conn->secondaryhostname = strdup(ftpc->newhost); + if(!conn->secondaryhostname) + return CURLE_OUT_OF_MEMORY; + + conn->bits.do_more = TRUE; + ftp_state(data, FTP_STOP); /* this phase is completed */ + + return result; +} + +static CURLcode ftp_state_port_resp(struct Curl_easy *data, + int ftpcode) +{ + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + ftpport fcmd = (ftpport)ftpc->count1; + CURLcode result = CURLE_OK; + + /* The FTP spec tells a positive response should have code 200. + Be more permissive here to tolerate deviant servers. */ + if(ftpcode / 100 != 2) { + /* the command failed */ + + if(EPRT == fcmd) { + infof(data, "disabling EPRT usage"); + conn->bits.ftp_use_eprt = FALSE; + } + fcmd++; + + if(fcmd == DONE) { + failf(data, "Failed to do PORT"); + result = CURLE_FTP_PORT_FAILED; + } + else + /* try next */ + result = ftp_state_use_port(data, fcmd); + } + else { + infof(data, "Connect data stream actively"); + ftp_state(data, FTP_STOP); /* end of DO phase */ + result = ftp_dophase_done(data, FALSE); + } + + return result; +} + +static int twodigit(const char *p) +{ + return (p[0]-'0') * 10 + (p[1]-'0'); +} + +static bool ftp_213_date(const char *p, int *year, int *month, int *day, + int *hour, int *minute, int *second) +{ + size_t len = strlen(p); + if(len < 14) + return FALSE; + *year = twodigit(&p[0]) * 100 + twodigit(&p[2]); + *month = twodigit(&p[4]); + *day = twodigit(&p[6]); + *hour = twodigit(&p[8]); + *minute = twodigit(&p[10]); + *second = twodigit(&p[12]); + + if((*month > 12) || (*day > 31) || (*hour > 23) || (*minute > 59) || + (*second > 60)) + return FALSE; + return TRUE; +} + +static CURLcode client_write_header(struct Curl_easy *data, + char *buf, size_t blen) +{ + /* Some replies from an FTP server are written to the client + * as CLIENTWRITE_HEADER, formatted as if they came from a + * HTTP conversation. + * In all protocols, CLIENTWRITE_HEADER data is only passed to + * the body write callback when data->set.include_header is set + * via CURLOPT_HEADER. + * For historic reasons, FTP never played this game and expects + * all its HEADERs to do that always. Set that flag during the + * call to Curl_client_write() so it does the right thing. + * + * Notice that we cannot enable this flag for FTP in general, + * as an FTP transfer might involve a HTTP proxy connection and + * headers from CONNECT should not automatically be part of the + * output. */ + CURLcode result; + int save = data->set.include_header; + data->set.include_header = TRUE; + result = Curl_client_write(data, CLIENTWRITE_HEADER, buf, blen); + data->set.include_header = save? TRUE:FALSE; + return result; +} + +static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, + int ftpcode) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + switch(ftpcode) { + case 213: + { + /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the + last .sss part is optional and means fractions of a second */ + int year, month, day, hour, minute, second; + if(ftp_213_date(&data->state.buffer[4], + &year, &month, &day, &hour, &minute, &second)) { + /* we have a time, reformat it */ + char timebuf[24]; + msnprintf(timebuf, sizeof(timebuf), + "%04d%02d%02d %02d:%02d:%02d GMT", + year, month, day, hour, minute, second); + /* now, convert this into a time() value: */ + data->info.filetime = Curl_getdate_capped(timebuf); + } + +#ifdef CURL_FTP_HTTPSTYLE_HEAD + /* If we asked for a time of the file and we actually got one as well, + we "emulate" an HTTP-style header in our output. */ + + if(data->req.no_body && + ftpc->file && + data->set.get_filetime && + (data->info.filetime >= 0) ) { + char headerbuf[128]; + int headerbuflen; + time_t filetime = data->info.filetime; + struct tm buffer; + const struct tm *tm = &buffer; + + result = Curl_gmtime(filetime, &buffer); + if(result) + return result; + + /* format: "Tue, 15 Nov 1994 12:45:26" */ + headerbuflen = msnprintf(headerbuf, sizeof(headerbuf), + "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", + Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + result = client_write_header(data, headerbuf, headerbuflen); + if(result) + return result; + } /* end of a ridiculous amount of conditionals */ +#endif + } + break; + default: + infof(data, "unsupported MDTM reply format"); + break; + case 550: /* 550 is used for several different problems, e.g. + "No such file or directory" or "Permission denied". + It does not mean that the file does not exist at all. */ + infof(data, "MDTM failed: file does not exist or permission problem," + " continuing"); + break; + } + + if(data->set.timecondition) { + if((data->info.filetime > 0) && (data->set.timevalue > 0)) { + switch(data->set.timecondition) { + case CURL_TIMECOND_IFMODSINCE: + default: + if(data->info.filetime <= data->set.timevalue) { + infof(data, "The requested document is not new enough"); + ftp->transfer = PPTRANSFER_NONE; /* mark to not transfer data */ + data->info.timecond = TRUE; + ftp_state(data, FTP_STOP); + return CURLE_OK; + } + break; + case CURL_TIMECOND_IFUNMODSINCE: + if(data->info.filetime > data->set.timevalue) { + infof(data, "The requested document is not old enough"); + ftp->transfer = PPTRANSFER_NONE; /* mark to not transfer data */ + data->info.timecond = TRUE; + ftp_state(data, FTP_STOP); + return CURLE_OK; + } + break; + } /* switch */ + } + else { + infof(data, "Skipping time comparison"); + } + } + + if(!result) + result = ftp_state_type(data); + + return result; +} + +static CURLcode ftp_state_type_resp(struct Curl_easy *data, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + if(ftpcode/100 != 2) { + /* "sasserftpd" and "(u)r(x)bot ftpd" both responds with 226 after a + successful 'TYPE I'. While that is not as RFC959 says, it is still a + positive response code and we allow that. */ + failf(data, "Couldn't set desired mode"); + return CURLE_FTP_COULDNT_SET_TYPE; + } + if(ftpcode != 200) + infof(data, "Got a %03d response code instead of the assumed 200", + ftpcode); + + if(instate == FTP_TYPE) + result = ftp_state_size(data, conn); + else if(instate == FTP_LIST_TYPE) + result = ftp_state_list(data); + else if(instate == FTP_RETR_TYPE) + result = ftp_state_retr_prequote(data); + else if(instate == FTP_STOR_TYPE) + result = ftp_state_stor_prequote(data); + + return result; +} + +static CURLcode ftp_state_retr(struct Curl_easy *data, + curl_off_t filesize) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + DEBUGF(infof(data, "ftp_state_retr()")); + if(data->set.max_filesize && (filesize > data->set.max_filesize)) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; + } + ftp->downloadsize = filesize; + + if(data->state.resume_from) { + /* We always (attempt to) get the size of downloads, so it is done before + this even when not doing resumes. */ + if(filesize == -1) { + infof(data, "ftp server doesn't support SIZE"); + /* We couldn't get the size and therefore we can't know if there really + is a part of the file left to get, although the server will just + close the connection when we start the connection so it won't cause + us any harm, just not make us exit as nicely. */ + } + else { + /* We got a file size report, so we check that there actually is a + part of the file left to get, or else we go home. */ + if(data->state.resume_from< 0) { + /* We're supposed to download the last abs(from) bytes */ + if(filesize < -data->state.resume_from) { + failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T + ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", + data->state.resume_from, filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* convert to size to download */ + ftp->downloadsize = -data->state.resume_from; + /* download from where? */ + data->state.resume_from = filesize - ftp->downloadsize; + } + else { + if(filesize < data->state.resume_from) { + failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T + ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", + data->state.resume_from, filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* Now store the number of bytes we are expected to download */ + ftp->downloadsize = filesize-data->state.resume_from; + } + } + + if(ftp->downloadsize == 0) { + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + infof(data, "File already completely downloaded"); + + /* Set ->transfer so that we won't get any error in ftp_done() + * because we didn't transfer the any file */ + ftp->transfer = PPTRANSFER_NONE; + ftp_state(data, FTP_STOP); + return CURLE_OK; + } + + /* Set resume file transfer offset */ + infof(data, "Instructs server to resume from offset %" + CURL_FORMAT_CURL_OFF_T, data->state.resume_from); + + result = Curl_pp_sendf(data, &ftpc->pp, "REST %" CURL_FORMAT_CURL_OFF_T, + data->state.resume_from); + if(!result) + ftp_state(data, FTP_RETR_REST); + } + else { + /* no resume */ + result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); + if(!result) + ftp_state(data, FTP_RETR); + } + + return result; +} + +static CURLcode ftp_state_size_resp(struct Curl_easy *data, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + curl_off_t filesize = -1; + char *buf = data->state.buffer; + + /* get the size from the ascii string: */ + if(ftpcode == 213) { + /* To allow servers to prepend "rubbish" in the response string, we scan + for all the digits at the end of the response and parse only those as a + number. */ + char *start = &buf[4]; + char *fdigit = strchr(start, '\r'); + if(fdigit) { + do + fdigit--; + while(ISDIGIT(*fdigit) && (fdigit > start)); + if(!ISDIGIT(*fdigit)) + fdigit++; + } + else + fdigit = start; + /* ignores parsing errors, which will make the size remain unknown */ + (void)curlx_strtoofft(fdigit, NULL, 10, &filesize); + + } + else if(ftpcode == 550) { /* "No such file or directory" */ + /* allow a SIZE failure for (resumed) uploads, when probing what command + to use */ + if(instate != FTP_STOR_SIZE) { + failf(data, "The file does not exist"); + return CURLE_REMOTE_FILE_NOT_FOUND; + } + } + + if(instate == FTP_SIZE) { +#ifdef CURL_FTP_HTTPSTYLE_HEAD + if(-1 != filesize) { + char clbuf[128]; + int clbuflen = msnprintf(clbuf, sizeof(clbuf), + "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", filesize); + result = client_write_header(data, clbuf, clbuflen); + if(result) + return result; + } +#endif + Curl_pgrsSetDownloadSize(data, filesize); + result = ftp_state_rest(data, data->conn); + } + else if(instate == FTP_RETR_SIZE) { + Curl_pgrsSetDownloadSize(data, filesize); + result = ftp_state_retr(data, filesize); + } + else if(instate == FTP_STOR_SIZE) { + data->state.resume_from = filesize; + result = ftp_state_ul_setup(data, TRUE); + } + + return result; +} + +static CURLcode ftp_state_rest_resp(struct Curl_easy *data, + struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + switch(instate) { + case FTP_REST: + default: +#ifdef CURL_FTP_HTTPSTYLE_HEAD + if(ftpcode == 350) { + char buffer[24]= { "Accept-ranges: bytes\r\n" }; + result = client_write_header(data, buffer, strlen(buffer)); + if(result) + return result; + } +#endif + result = ftp_state_prepare_transfer(data); + break; + + case FTP_RETR_REST: + if(ftpcode != 350) { + failf(data, "Couldn't use REST"); + result = CURLE_FTP_COULDNT_USE_REST; + } + else { + result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); + if(!result) + ftp_state(data, FTP_RETR); + } + break; + } + + return result; +} + +static CURLcode ftp_state_stor_resp(struct Curl_easy *data, + int ftpcode, ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + if(ftpcode >= 400) { + failf(data, "Failed FTP upload: %0d", ftpcode); + ftp_state(data, FTP_STOP); + /* oops, we never close the sockets! */ + return CURLE_UPLOAD_FAILED; + } + + conn->proto.ftpc.state_saved = instate; + + /* PORT means we are now awaiting the server to connect to us. */ + if(data->set.ftp_use_port) { + bool connected; + + ftp_state(data, FTP_STOP); /* no longer in STOR state */ + + result = AllowServerConnect(data, &connected); + if(result) + return result; + + if(!connected) { + struct ftp_conn *ftpc = &conn->proto.ftpc; + infof(data, "Data conn was not available immediately"); + ftpc->wait_data_conn = TRUE; + } + + return CURLE_OK; + } + return InitiateTransfer(data); +} + +/* for LIST and RETR responses */ +static CURLcode ftp_state_get_resp(struct Curl_easy *data, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; + + if((ftpcode == 150) || (ftpcode == 125)) { + + /* + A; + 150 Opening BINARY mode data connection for /etc/passwd (2241 + bytes). (ok, the file is being transferred) + + B: + 150 Opening ASCII mode data connection for /bin/ls + + C: + 150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes). + + D: + 150 Opening ASCII mode data connection for [file] (0.0.0.0,0) (545 bytes) + + E: + 125 Data connection already open; Transfer starting. */ + + curl_off_t size = -1; /* default unknown size */ + + + /* + * It appears that there are FTP-servers that return size 0 for files when + * SIZE is used on the file while being in BINARY mode. To work around + * that (stupid) behavior, we attempt to parse the RETR response even if + * the SIZE returned size zero. + * + * Debugging help from Salvatore Sorrentino on February 26, 2003. + */ + + if((instate != FTP_LIST) && + !data->state.prefer_ascii && + !data->set.ignorecl && + (ftp->downloadsize < 1)) { + /* + * It seems directory listings either don't show the size or very + * often uses size 0 anyway. ASCII transfers may very well turn out + * that the transferred amount of data is not the same as this line + * tells, why using this number in those cases only confuses us. + * + * Example D above makes this parsing a little tricky */ + char *bytes; + char *buf = data->state.buffer; + bytes = strstr(buf, " bytes"); + if(bytes) { + long in = (long)(--bytes-buf); + /* this is a hint there is size information in there! ;-) */ + while(--in) { + /* scan for the left parenthesis and break there */ + if('(' == *bytes) + break; + /* skip only digits */ + if(!ISDIGIT(*bytes)) { + bytes = NULL; + break; + } + /* one more estep backwards */ + bytes--; + } + /* if we have nothing but digits: */ + if(bytes) { + ++bytes; + /* get the number! */ + (void)curlx_strtoofft(bytes, NULL, 10, &size); + } + } + } + else if(ftp->downloadsize > -1) + size = ftp->downloadsize; + + if(size > data->req.maxdownload && data->req.maxdownload > 0) + size = data->req.size = data->req.maxdownload; + else if((instate != FTP_LIST) && (data->state.prefer_ascii)) + size = -1; /* kludge for servers that understate ASCII mode file size */ + + infof(data, "Maxdownload = %" CURL_FORMAT_CURL_OFF_T, + data->req.maxdownload); + + if(instate != FTP_LIST) + infof(data, "Getting file with size: %" CURL_FORMAT_CURL_OFF_T, + size); + + /* FTP download: */ + conn->proto.ftpc.state_saved = instate; + conn->proto.ftpc.retr_size_saved = size; + + if(data->set.ftp_use_port) { + bool connected; + + result = AllowServerConnect(data, &connected); + if(result) + return result; + + if(!connected) { + struct ftp_conn *ftpc = &conn->proto.ftpc; + infof(data, "Data conn was not available immediately"); + ftp_state(data, FTP_STOP); + ftpc->wait_data_conn = TRUE; + } + } + else + return InitiateTransfer(data); + } + else { + if((instate == FTP_LIST) && (ftpcode == 450)) { + /* simply no matching files in the dir listing */ + ftp->transfer = PPTRANSFER_NONE; /* don't download anything */ + ftp_state(data, FTP_STOP); /* this phase is over */ + } + else { + failf(data, "RETR response: %03d", ftpcode); + return instate == FTP_RETR && ftpcode == 550? + CURLE_REMOTE_FILE_NOT_FOUND: + CURLE_FTP_COULDNT_RETR_FILE; + } + } + + return result; +} + +/* after USER, PASS and ACCT */ +static CURLcode ftp_state_loggedin(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + if(conn->bits.ftp_use_control_ssl) { + /* PBSZ = PROTECTION BUFFER SIZE. + + The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says: + + Specifically, the PROT command MUST be preceded by a PBSZ + command and a PBSZ command MUST be preceded by a successful + security data exchange (the TLS negotiation in this case) + + ... (and on page 8): + + Thus the PBSZ command must still be issued, but must have a + parameter of '0' to indicate that no buffering is taking place + and the data connection should not be encapsulated. + */ + result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "PBSZ %d", 0); + if(!result) + ftp_state(data, FTP_PBSZ); + } + else { + result = ftp_state_pwd(data, conn); + } + return result; +} + +/* for USER and PASS responses */ +static CURLcode ftp_state_user_resp(struct Curl_easy *data, + int ftpcode) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + /* some need password anyway, and others just return 2xx ignored */ + if((ftpcode == 331) && (ftpc->state == FTP_USER)) { + /* 331 Password required for ... + (the server requires to send the user's password too) */ + result = Curl_pp_sendf(data, &ftpc->pp, "PASS %s", + conn->passwd?conn->passwd:""); + if(!result) + ftp_state(data, FTP_PASS); + } + else if(ftpcode/100 == 2) { + /* 230 User ... logged in. + (the user logged in with or without password) */ + result = ftp_state_loggedin(data); + } + 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, FTP_ACCT); + } + else { + failf(data, "ACCT requested but none available"); + result = CURLE_LOGIN_DENIED; + } + } + else { + /* All other response codes, like: + + 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) { + /* 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, FTP_USER); + } + } + else { + failf(data, "Access denied: %03d", ftpcode); + result = CURLE_LOGIN_DENIED; + } + } + return result; +} + +/* for ACCT response */ +static CURLcode ftp_state_acct_resp(struct Curl_easy *data, + int ftpcode) +{ + CURLcode result = CURLE_OK; + if(ftpcode != 230) { + failf(data, "ACCT rejected by server: %03d", ftpcode); + result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */ + } + else + result = ftp_state_loggedin(data); + + return result; +} + + +static CURLcode ftp_statemachine(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int ftpcode; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + static const char * const ftpauth[] = { "SSL", "TLS" }; + size_t nread = 0; + + if(pp->sendleft) + return Curl_pp_flushsend(data, pp); + + result = ftp_readresp(data, sock, pp, &ftpcode, &nread); + if(result) + return result; + + if(ftpcode) { + /* we have now received a full FTP server response */ + switch(ftpc->state) { + case FTP_WAIT220: + if(ftpcode == 230) { + /* 230 User logged in - already! Take as 220 if TLS required. */ + if(data->set.use_ssl <= CURLUSESSL_TRY || + conn->bits.ftp_use_control_ssl) + return ftp_state_user_resp(data, ftpcode); + } + else if(ftpcode != 220) { + failf(data, "Got a %03d ftp-server response when 220 was expected", + ftpcode); + return CURLE_WEIRD_SERVER_REPLY; + } + + /* We have received a 220 response fine, now we proceed. */ +#ifdef HAVE_GSSAPI + if(data->set.krb) { + /* If not anonymous login, try a secure login. Note that this + procedure is still BLOCKING. */ + + Curl_sec_request_prot(conn, "private"); + /* We set private first as default, in case the line below fails to + set a valid level */ + Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]); + + if(Curl_sec_login(data, conn)) { + failf(data, "secure login failed"); + return CURLE_WEIRD_SERVER_REPLY; + } + infof(data, "Authentication successful"); + } +#endif + + if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) { + /* We don't have a SSL/TLS control connection yet, but FTPS is + requested. Try a FTPS connection now */ + + ftpc->count3 = 0; + switch(data->set.ftpsslauth) { + case CURLFTPAUTH_DEFAULT: + case CURLFTPAUTH_SSL: + ftpc->count2 = 1; /* add one to get next */ + ftpc->count1 = 0; + break; + case CURLFTPAUTH_TLS: + ftpc->count2 = -1; /* subtract one to get next */ + ftpc->count1 = 1; + break; + default: + failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d", + (int)data->set.ftpsslauth); + return CURLE_UNKNOWN_OPTION; /* we don't know what to do */ + } + result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", + ftpauth[ftpc->count1]); + if(!result) + ftp_state(data, FTP_AUTH); + } + else + result = ftp_state_user(data, conn); + break; + + case FTP_AUTH: + /* we have gotten the response to a previous AUTH command */ + + if(pp->cache_size) + return CURLE_WEIRD_SERVER_REPLY; /* Forbid pipelining in response. */ + + /* RFC2228 (page 5) says: + * + * If the server is willing to accept the named security mechanism, + * and does not require any security data, it must respond with + * reply code 234/334. + */ + + if((ftpcode == 234) || (ftpcode == 334)) { + /* this was BLOCKING, keep it so for now */ + bool done; + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + if(result) { + /* we failed and bail out */ + return CURLE_USE_SSL_FAILED; + } + } + result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, &done); + if(!result) { + conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */ + conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */ + result = ftp_state_user(data, conn); + } + } + else if(ftpc->count3 < 1) { + ftpc->count3++; + ftpc->count1 += ftpc->count2; /* get next attempt */ + result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", + ftpauth[ftpc->count1]); + /* remain in this same state */ + } + else { + if(data->set.use_ssl > CURLUSESSL_TRY) + /* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */ + result = CURLE_USE_SSL_FAILED; + else + /* ignore the failure and continue */ + result = ftp_state_user(data, conn); + } + break; + + case FTP_USER: + case FTP_PASS: + result = ftp_state_user_resp(data, ftpcode); + break; + + case FTP_ACCT: + result = ftp_state_acct_resp(data, ftpcode); + break; + + case FTP_PBSZ: + result = + Curl_pp_sendf(data, &ftpc->pp, "PROT %c", + data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); + if(!result) + ftp_state(data, FTP_PROT); + break; + + case FTP_PROT: + if(ftpcode/100 == 2) + /* We have enabled SSL for the data connection! */ + conn->bits.ftp_use_data_ssl = + (data->set.use_ssl != CURLUSESSL_CONTROL) ? TRUE : FALSE; + /* FTP servers typically responds with 500 if they decide to reject + our 'P' request */ + else if(data->set.use_ssl > CURLUSESSL_CONTROL) + /* we failed and bails out */ + return CURLE_USE_SSL_FAILED; + + if(data->set.ftp_ccc) { + /* CCC - Clear Command Channel + */ + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC"); + if(!result) + ftp_state(data, FTP_CCC); + } + else + result = ftp_state_pwd(data, conn); + break; + + case FTP_CCC: + if(ftpcode < 500) { + /* First shut down the SSL layer (note: this call will block) */ + result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET); + + if(result) + failf(data, "Failed to clear the command channel (CCC)"); + } + if(!result) + /* Then continue as normal */ + result = ftp_state_pwd(data, conn); + break; + + case FTP_PWD: + if(ftpcode == 257) { + char *ptr = &data->state.buffer[4]; /* start on the first letter */ + const size_t buf_size = data->set.buffer_size; + char *dir; + bool entry_extracted = FALSE; + + dir = malloc(nread + 1); + if(!dir) + return CURLE_OUT_OF_MEMORY; + + /* Reply format is like + 257<space>[rubbish]"<directory-name>"<space><commentary> and the + RFC959 says + + 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 < &data->state.buffer[buf_size] + && *ptr != '\n' && *ptr != '\0' && *ptr != '"') + ptr++; + + if('\"' == *ptr) { + /* it started good */ + char *store; + ptr++; + for(store = dir; *ptr;) { + if('\"' == *ptr) { + if('\"' == ptr[1]) { + /* "quote-doubling" */ + *store = ptr[1]; + ptr++; + } + else { + /* end of path */ + entry_extracted = TRUE; + break; /* get out of this loop */ + } + } + else + *store = *ptr; + store++; + ptr++; + } + *store = '\0'; /* null-terminate */ + } + if(entry_extracted) { + /* If the path name does not look like an absolute path (i.e.: it + does not start with a '/'), we probably need some server-dependent + adjustments. For example, this is the case when connecting to + an OS400 FTP server: this server supports two name syntaxes, + the default one being incompatible with standard paths. In + addition, this server switches automatically to the regular path + syntax when one is encountered in a command: this results in + having an entrypath in the wrong syntax when later used in CWD. + The method used here is to check the server OS: we do it only + if the path name looks strange to minimize overhead on other + systems. */ + + if(!ftpc->server_os && dir[0] != '/') { + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST"); + if(result) { + free(dir); + return result; + } + Curl_safefree(ftpc->entrypath); + ftpc->entrypath = dir; /* remember this */ + infof(data, "Entry path is '%s'", ftpc->entrypath); + /* also save it where getinfo can access it: */ + data->state.most_recent_ftp_entrypath = ftpc->entrypath; + ftp_state(data, FTP_SYST); + break; + } + + Curl_safefree(ftpc->entrypath); + ftpc->entrypath = dir; /* remember this */ + infof(data, "Entry path is '%s'", ftpc->entrypath); + /* also save it where getinfo can access it: */ + data->state.most_recent_ftp_entrypath = ftpc->entrypath; + } + else { + /* couldn't get the path */ + free(dir); + infof(data, "Failed to figure out path"); + } + } + ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ + DEBUGF(infof(data, "protocol connect phase DONE")); + break; + + case FTP_SYST: + if(ftpcode == 215) { + char *ptr = &data->state.buffer[4]; /* start on the first letter */ + char *os; + char *store; + + os = malloc(nread + 1); + if(!os) + return CURLE_OUT_OF_MEMORY; + + /* Reply format is like + 215<space><OS-name><space><commentary> + */ + while(*ptr == ' ') + ptr++; + for(store = os; *ptr && *ptr != ' ';) + *store++ = *ptr++; + *store = '\0'; /* null-terminate */ + + /* Check for special servers here. */ + + if(strcasecompare(os, "OS/400")) { + /* Force OS400 name format 1. */ + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1"); + if(result) { + free(os); + return result; + } + /* remember target server OS */ + Curl_safefree(ftpc->server_os); + ftpc->server_os = os; + ftp_state(data, FTP_NAMEFMT); + break; + } + /* Nothing special for the target server. */ + /* remember target server OS */ + Curl_safefree(ftpc->server_os); + ftpc->server_os = os; + } + else { + /* Cannot identify server OS. Continue anyway and cross fingers. */ + } + + ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ + DEBUGF(infof(data, "protocol connect phase DONE")); + break; + + case FTP_NAMEFMT: + if(ftpcode == 250) { + /* Name format change successful: reload initial path. */ + ftp_state_pwd(data, conn); + break; + } + + ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ + DEBUGF(infof(data, "protocol connect phase DONE")); + break; + + case FTP_QUOTE: + case FTP_POSTQUOTE: + case FTP_RETR_PREQUOTE: + case FTP_STOR_PREQUOTE: + if((ftpcode >= 400) && !ftpc->count2) { + /* failure response code, and not allowed to fail */ + failf(data, "QUOT command failed with %03d", ftpcode); + result = CURLE_QUOTE_ERROR; + } + else + result = ftp_state_quote(data, FALSE, ftpc->state); + break; + + case FTP_CWD: + if(ftpcode/100 != 2) { + /* failure to CWD there */ + if(data->set.ftp_create_missing_dirs && + ftpc->cwdcount && !ftpc->count2) { + /* try making it */ + ftpc->count2++; /* counter to prevent CWD-MKD loops */ + + /* count3 is set to allow MKD to fail once per dir. In the case when + CWD fails and then MKD fails (due to another session raced it to + create the dir) this then allows for a second try to CWD to it. */ + ftpc->count3 = (data->set.ftp_create_missing_dirs == 2) ? 1 : 0; + + result = Curl_pp_sendf(data, &ftpc->pp, "MKD %s", + ftpc->dirs[ftpc->cwdcount - 1]); + if(!result) + ftp_state(data, FTP_MKD); + } + else { + /* return failure */ + failf(data, "Server denied you to change to the given directory"); + ftpc->cwdfail = TRUE; /* don't remember this path as we failed + to enter it */ + result = CURLE_REMOTE_ACCESS_DENIED; + } + } + else { + /* success */ + ftpc->count2 = 0; + if(++ftpc->cwdcount <= ftpc->dirdepth) + /* send next CWD */ + result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", + ftpc->dirs[ftpc->cwdcount - 1]); + else + result = ftp_state_mdtm(data); + } + break; + + case FTP_MKD: + if((ftpcode/100 != 2) && !ftpc->count3--) { + /* failure to MKD the dir */ + failf(data, "Failed to MKD dir: %03d", ftpcode); + result = CURLE_REMOTE_ACCESS_DENIED; + } + else { + ftp_state(data, FTP_CWD); + /* send CWD */ + result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", + ftpc->dirs[ftpc->cwdcount - 1]); + } + break; + + case FTP_MDTM: + result = ftp_state_mdtm_resp(data, ftpcode); + break; + + case FTP_TYPE: + case FTP_LIST_TYPE: + case FTP_RETR_TYPE: + case FTP_STOR_TYPE: + result = ftp_state_type_resp(data, ftpcode, ftpc->state); + break; + + case FTP_SIZE: + case FTP_RETR_SIZE: + case FTP_STOR_SIZE: + result = ftp_state_size_resp(data, ftpcode, ftpc->state); + break; + + case FTP_REST: + case FTP_RETR_REST: + result = ftp_state_rest_resp(data, conn, ftpcode, ftpc->state); + break; + + case FTP_PRET: + if(ftpcode != 200) { + /* there only is this one standard OK return code. */ + failf(data, "PRET command not accepted: %03d", ftpcode); + return CURLE_FTP_PRET_FAILED; + } + result = ftp_state_use_pasv(data, conn); + break; + + case FTP_PASV: + result = ftp_state_pasv_resp(data, ftpcode); + break; + + case FTP_PORT: + result = ftp_state_port_resp(data, ftpcode); + break; + + case FTP_LIST: + case FTP_RETR: + result = ftp_state_get_resp(data, ftpcode, ftpc->state); + break; + + case FTP_STOR: + result = ftp_state_stor_resp(data, ftpcode, ftpc->state); + break; + + case FTP_QUIT: + /* fallthrough, just stop! */ + default: + /* internal error */ + ftp_state(data, FTP_STOP); + break; + } + } /* if(ftpcode) */ + + return result; +} + + +/* called repeatedly until done from multi.c */ +static CURLcode ftp_multi_statemach(struct Curl_easy *data, + bool *done) +{ + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + CURLcode result = Curl_pp_statemach(data, &ftpc->pp, FALSE, FALSE); + + /* Check for the state outside of the Curl_socket_check() return code checks + since at times we are in fact already in this state when this function + gets called. */ + *done = (ftpc->state == FTP_STOP) ? TRUE : FALSE; + + return result; +} + +static CURLcode ftp_block_statemach(struct Curl_easy *data, + struct connectdata *conn) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + CURLcode result = CURLE_OK; + + while(ftpc->state != FTP_STOP) { + result = Curl_pp_statemach(data, pp, TRUE, TRUE /* disconnecting */); + if(result) + break; + } + + return result; +} + +/* + * ftp_connect() should do everything that is to be considered a part of + * the connection phase. + * + * The variable 'done' points to will be TRUE if the protocol-layer connect + * phase is done when this function returns, or FALSE if not. + * + */ +static CURLcode ftp_connect(struct Curl_easy *data, + bool *done) /* see description above */ +{ + CURLcode result; + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + + *done = FALSE; /* default to not done yet */ + + /* We always support persistent connections on ftp */ + connkeep(conn, "FTP default"); + + PINGPONG_SETUP(pp, ftp_statemachine, ftp_endofresp); + + if(conn->handler->flags & PROTOPT_SSL) { + /* BLOCKING */ + result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done); + if(result) + return result; + conn->bits.ftp_use_control_ssl = TRUE; + } + + Curl_pp_setup(pp); /* once per transfer */ + Curl_pp_init(data, pp); /* init the generic pingpong data */ + + /* When we connect, we start in the state where we await the 220 + response */ + ftp_state(data, FTP_WAIT220); + + result = ftp_multi_statemach(data, done); + + return result; +} + +/*********************************************************************** + * + * ftp_done() + * + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. + */ +static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, + bool premature) +{ + struct connectdata *conn = data->conn; + struct FTP *ftp = data->req.p.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + ssize_t nread; + int ftpcode; + CURLcode result = CURLE_OK; + char *rawPath = NULL; + size_t pathLen = 0; + + if(!ftp) + return CURLE_OK; + + switch(status) { + case CURLE_BAD_DOWNLOAD_RESUME: + case CURLE_FTP_WEIRD_PASV_REPLY: + case CURLE_FTP_PORT_FAILED: + case CURLE_FTP_ACCEPT_FAILED: + case CURLE_FTP_ACCEPT_TIMEOUT: + case CURLE_FTP_COULDNT_SET_TYPE: + case CURLE_FTP_COULDNT_RETR_FILE: + case CURLE_PARTIAL_FILE: + case CURLE_UPLOAD_FAILED: + case CURLE_REMOTE_ACCESS_DENIED: + case CURLE_FILESIZE_EXCEEDED: + case CURLE_REMOTE_FILE_NOT_FOUND: + case CURLE_WRITE_ERROR: + /* the connection stays alive fine even though this happened */ + /* fall-through */ + case CURLE_OK: /* doesn't affect the control connection's status */ + if(!premature) + break; + + /* until we cope better with prematurely ended requests, let them + * fallback as if in complete failure */ + /* FALLTHROUGH */ + default: /* by default, an error means the control connection is + wedged and should not be used anymore */ + 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"); + result = status; /* use the already set error code */ + break; + } + + if(data->state.wildcardmatch) { + if(data->set.chunk_end && ftpc->file) { + Curl_set_in_callback(data, true); + data->set.chunk_end(data->set.wildcardptr); + Curl_set_in_callback(data, false); + } + ftpc->known_filesize = -1; + } + + if(!result) + /* get the url-decoded "raw" path */ + result = Curl_urldecode(ftp->path, 0, &rawPath, &pathLen, + REJECT_CTRL); + if(result) { + /* 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 */ + free(ftpc->prevpath); + ftpc->prevpath = NULL; /* no path remembering */ + } + else { /* remember working directory for connection reuse */ + if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (rawPath[0] == '/')) + free(rawPath); /* full path => no CWDs happened => keep ftpc->prevpath */ + else { + free(ftpc->prevpath); + + if(!ftpc->cwdfail) { + if(data->set.ftp_filemethod == FTPFILE_NOCWD) + pathLen = 0; /* relative path => working directory is FTP home */ + else + pathLen -= ftpc->file?strlen(ftpc->file):0; /* file is url-decoded */ + + rawPath[pathLen] = '\0'; + ftpc->prevpath = rawPath; + } + else { + free(rawPath); + ftpc->prevpath = NULL; /* no path */ + } + } + + if(ftpc->prevpath) + infof(data, "Remembering we are in dir \"%s\"", ftpc->prevpath); + } + + /* free the dir tree and file parts */ + freedirs(ftpc); + + /* shut down the socket to inform the server we're done */ + +#ifdef _WIN32_WCE + shutdown(conn->sock[SECONDARYSOCKET], 2); /* SD_BOTH */ +#endif + + if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { + if(!result && ftpc->dont_check && data->req.maxdownload > 0) { + /* partial download completed */ + result = Curl_pp_sendf(data, pp, "%s", "ABOR"); + if(result) { + 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 */ + } + } + + close_secondarysocket(data, conn); + } + + if(!result && (ftp->transfer == PPTRANSFER_BODY) && ftpc->ctl_valid && + pp->pending_resp && !premature) { + /* + * Let's see what the server says about the transfer we just performed, + * but lower the timeout as sometimes this connection has died while the + * data has been transferred. This happens when doing through NATs etc that + * abandon old silent connections. + */ + timediff_t old_time = pp->response_time; + + pp->response_time = 60*1000; /* give it only a minute for now */ + pp->response = Curl_now(); /* timeout relative now */ + + result = Curl_GetFTPResponse(data, &nread, &ftpcode); + + pp->response_time = old_time; /* set this back to previous value */ + + if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) { + 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 */ + } + + if(result) { + Curl_safefree(ftp->pathalloc); + return result; + } + + if(ftpc->dont_check && data->req.maxdownload > 0) { + /* we have just 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"); + return result; + } + + if(!ftpc->dont_check) { + /* 226 Transfer complete, 250 Requested file action okay, completed. */ + switch(ftpcode) { + case 226: + case 250: + break; + case 552: + failf(data, "Exceeded storage allocation"); + result = CURLE_REMOTE_DISK_FULL; + break; + default: + failf(data, "server did not report OK, got %d", ftpcode); + result = CURLE_PARTIAL_FILE; + break; + } + } + } + + if(result || premature) + /* the response code from the transfer showed an error already so no + use checking further */ + ; + else if(data->state.upload) { + if((-1 != data->state.infilesize) && + (data->state.infilesize != data->req.writebytecount) && + !data->set.crlf && + (ftp->transfer == PPTRANSFER_BODY)) { + failf(data, "Uploaded unaligned file size (%" CURL_FORMAT_CURL_OFF_T + " out of %" CURL_FORMAT_CURL_OFF_T " bytes)", + data->req.writebytecount, data->state.infilesize); + result = CURLE_PARTIAL_FILE; + } + } + else { + if((-1 != data->req.size) && + (data->req.size != data->req.bytecount) && +#ifdef CURL_DO_LINEEND_CONV + /* Most FTP servers don't adjust their file SIZE response for CRLFs, so + * we'll check to see if the discrepancy can be explained by the number + * of CRLFs we've changed to LFs. + */ + ((data->req.size + data->state.crlf_conversions) != + data->req.bytecount) && +#endif /* CURL_DO_LINEEND_CONV */ + (data->req.maxdownload != data->req.bytecount)) { + failf(data, "Received only partial file: %" CURL_FORMAT_CURL_OFF_T + " bytes", data->req.bytecount); + result = CURLE_PARTIAL_FILE; + } + else if(!ftpc->dont_check && + !data->req.bytecount && + (data->req.size>0)) { + failf(data, "No data was received"); + result = CURLE_FTP_COULDNT_RETR_FILE; + } + } + + /* clear these for next connection */ + ftp->transfer = PPTRANSFER_BODY; + ftpc->dont_check = FALSE; + + /* Send any post-transfer QUOTE strings? */ + if(!status && !result && !premature && data->set.postquote) + result = ftp_sendquote(data, conn, data->set.postquote); + Curl_safefree(ftp->pathalloc); + return result; +} + +/*********************************************************************** + * + * ftp_sendquote() + * + * Where a 'quote' means a list of custom commands to send to the server. + * The quote list is passed as an argument. + * + * BLOCKING + */ + +static +CURLcode ftp_sendquote(struct Curl_easy *data, + struct connectdata *conn, struct curl_slist *quote) +{ + struct curl_slist *item; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + + item = quote; + while(item) { + if(item->data) { + ssize_t nread; + char *cmd = item->data; + bool acceptfail = FALSE; + CURLcode result; + int ftpcode = 0; + + /* if a command starts with an asterisk, which a legal FTP command never + can, the command will be allowed to fail without it causing any + aborts or cancels etc. It will cause libcurl to act as if the command + is successful, whatever the server responds. */ + + if(cmd[0] == '*') { + cmd++; + acceptfail = TRUE; + } + + result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); + if(!result) { + pp->response = Curl_now(); /* timeout relative now */ + result = Curl_GetFTPResponse(data, &nread, &ftpcode); + } + if(result) + return result; + + if(!acceptfail && (ftpcode >= 400)) { + failf(data, "QUOT string not accepted: %s", cmd); + return CURLE_QUOTE_ERROR; + } + } + + item = item->next; + } + + return CURLE_OK; +} + +/*********************************************************************** + * + * ftp_need_type() + * + * Returns TRUE if we in the current situation should send TYPE + */ +static int ftp_need_type(struct connectdata *conn, + bool ascii_wanted) +{ + return conn->proto.ftpc.transfertype != (ascii_wanted?'A':'I'); +} + +/*********************************************************************** + * + * ftp_nb_type() + * + * Set TYPE. We only deal with ASCII or BINARY so this function + * sets one of them. + * If the transfer type is not sent, simulate on OK response in newstate + */ +static CURLcode ftp_nb_type(struct Curl_easy *data, + struct connectdata *conn, + bool ascii, ftpstate newstate) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + CURLcode result; + char want = (char)(ascii?'A':'I'); + + if(ftpc->transfertype == want) { + ftp_state(data, newstate); + return ftp_state_type_resp(data, 200, newstate); + } + + result = Curl_pp_sendf(data, &ftpc->pp, "TYPE %c", want); + if(!result) { + ftp_state(data, newstate); + + /* keep track of our current transfer type */ + ftpc->transfertype = want; + } + return result; +} + +/*************************************************************************** + * + * ftp_pasv_verbose() + * + * This function only outputs some informationals about this second connection + * when we've issued a PASV command before and thus we have connected to a + * possibly new IP address. + * + */ +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void +ftp_pasv_verbose(struct Curl_easy *data, + struct Curl_addrinfo *ai, + char *newhost, /* ascii version */ + int port) +{ + char buf[256]; + Curl_printable_address(ai, buf, sizeof(buf)); + infof(data, "Connecting to %s (%s) port %d", newhost, buf, port); +} +#endif + +/* + * ftp_do_more() + * + * This function shall be called when the second FTP (data) connection is + * connected. + * + * 'complete' can return 0 for incomplete, 1 for done and -1 for go back + * (which basically is only for when PASV is being sent to retry a failed + * EPSV). + */ + +static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) +{ + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + CURLcode result = CURLE_OK; + bool connected = FALSE; + bool complete = FALSE; + + /* the ftp struct is inited in ftp_connect(). If we are connecting to an HTTP + * proxy then the state will not be valid until after that connection is + * complete */ + struct FTP *ftp = NULL; + + /* if the second connection isn't done yet, wait for it to have + * connected to the remote host. When using proxy tunneling, this + * means the tunnel needs to have been establish. However, we + * can not expect the remote host to talk to us in any way yet. + * So, when using ftps: the SSL handshake will not start until we + * tell the remote server that we are there. */ + if(conn->cfilter[SECONDARYSOCKET]) { + result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &connected); + if(result || !Curl_conn_is_ip_connected(data, SECONDARYSOCKET)) { + if(result && (ftpc->count1 == 0)) { + *completep = -1; /* go back to DOING please */ + /* this is a EPSV connect failing, try PASV instead */ + return ftp_epsv_disable(data, conn); + } + return result; + } + } + + /* Curl_proxy_connect might have moved the protocol state */ + ftp = data->req.p.ftp; + + if(ftpc->state) { + /* already in a state so skip the initial commands. + They are only done to kickstart the do_more state */ + result = ftp_multi_statemach(data, &complete); + + *completep = (int)complete; + + /* if we got an error or if we don't wait for a data connection return + immediately */ + if(result || !ftpc->wait_data_conn) + return result; + + /* if we reach the end of the FTP state machine here, *complete will be + TRUE but so is ftpc->wait_data_conn, which says we need to wait for the + data connection and therefore we're not actually complete */ + *completep = 0; + } + + if(ftp->transfer <= PPTRANSFER_INFO) { + /* a transfer is about to take place, or if not a file name was given + so we'll do a SIZE on it later and then we need the right TYPE first */ + + if(ftpc->wait_data_conn) { + bool serv_conned; + + result = ReceivedServerConnect(data, &serv_conned); + if(result) + return result; /* Failed to accept data connection */ + + if(serv_conned) { + /* It looks data connection is established */ + result = AcceptServerConnect(data); + ftpc->wait_data_conn = FALSE; + if(!result) + result = InitiateTransfer(data); + + if(result) + return result; + + *completep = 1; /* this state is now complete when the server has + connected back to us */ + } + } + else if(data->state.upload) { + result = ftp_nb_type(data, conn, data->state.prefer_ascii, + FTP_STOR_TYPE); + if(result) + return result; + + result = ftp_multi_statemach(data, &complete); + *completep = (int)complete; + } + else { + /* download */ + ftp->downloadsize = -1; /* unknown as of yet */ + + result = Curl_range(data); + + if(result == CURLE_OK && data->req.maxdownload >= 0) { + /* Don't check for successful transfer */ + ftpc->dont_check = TRUE; + } + + if(result) + ; + else if(data->state.list_only || !ftpc->file) { + /* The specified path ends with a slash, and therefore we think this + is a directory that is requested, use LIST. But before that we + need to set ASCII transfer mode. */ + + /* But only if a body transfer was requested. */ + if(ftp->transfer == PPTRANSFER_BODY) { + result = ftp_nb_type(data, conn, TRUE, FTP_LIST_TYPE); + if(result) + return result; + } + /* otherwise just fall through */ + } + else { + result = ftp_nb_type(data, conn, data->state.prefer_ascii, + FTP_RETR_TYPE); + if(result) + return result; + } + + result = ftp_multi_statemach(data, &complete); + *completep = (int)complete; + } + return result; + } + + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + + if(!ftpc->wait_data_conn) { + /* no waiting for the data connection so this is now complete */ + *completep = 1; + DEBUGF(infof(data, "DO-MORE phase ends with %d", (int)result)); + } + + return result; +} + + + +/*********************************************************************** + * + * ftp_perform() + * + * This is the actual DO function for FTP. Get a file/directory according to + * the options previously setup. + */ + +static +CURLcode ftp_perform(struct Curl_easy *data, + bool *connected, /* connect status after PASV / PORT */ + bool *dophase_done) +{ + /* this is FTP and no proxy */ + CURLcode result = CURLE_OK; + + DEBUGF(infof(data, "DO phase starts")); + + if(data->req.no_body) { + /* requested no body means no transfer... */ + struct FTP *ftp = data->req.p.ftp; + ftp->transfer = PPTRANSFER_INFO; + } + + *dophase_done = FALSE; /* not done yet */ + + /* start the first command in the DO phase */ + result = ftp_state_quote(data, TRUE, FTP_QUOTE); + if(result) + return result; + + /* run the state-machine */ + result = ftp_multi_statemach(data, dophase_done); + + *connected = Curl_conn_is_connected(data->conn, SECONDARYSOCKET); + + infof(data, "ftp_perform ends with SECONDARY: %d", *connected); + + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete1")); + } + + return result; +} + +static void wc_data_dtor(void *ptr) +{ + struct ftp_wc *ftpwc = ptr; + if(ftpwc && ftpwc->parser) + Curl_ftp_parselist_data_free(&ftpwc->parser); + free(ftpwc); +} + +static CURLcode init_wc_data(struct Curl_easy *data) +{ + char *last_slash; + struct FTP *ftp = data->req.p.ftp; + char *path = ftp->path; + struct WildcardData *wildcard = data->wildcard; + CURLcode result = CURLE_OK; + struct ftp_wc *ftpwc = NULL; + + last_slash = strrchr(ftp->path, '/'); + if(last_slash) { + last_slash++; + if(last_slash[0] == '\0') { + wildcard->state = CURLWC_CLEAN; + result = ftp_parse_url_path(data); + return result; + } + wildcard->pattern = strdup(last_slash); + if(!wildcard->pattern) + return CURLE_OUT_OF_MEMORY; + last_slash[0] = '\0'; /* cut file from path */ + } + else { /* there is only 'wildcard pattern' or nothing */ + if(path[0]) { + wildcard->pattern = strdup(path); + if(!wildcard->pattern) + return CURLE_OUT_OF_MEMORY; + path[0] = '\0'; + } + else { /* only list */ + wildcard->state = CURLWC_CLEAN; + result = ftp_parse_url_path(data); + return result; + } + } + + /* program continues only if URL is not ending with slash, allocate needed + resources for wildcard transfer */ + + /* allocate ftp protocol specific wildcard data */ + ftpwc = calloc(1, sizeof(struct ftp_wc)); + if(!ftpwc) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + + /* INITIALIZE parselist structure */ + ftpwc->parser = Curl_ftp_parselist_data_alloc(); + if(!ftpwc->parser) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + + wildcard->ftpwc = ftpwc; /* put it to the WildcardData tmp pointer */ + wildcard->dtor = wc_data_dtor; + + /* wildcard does not support NOCWD option (assert it?) */ + if(data->set.ftp_filemethod == FTPFILE_NOCWD) + data->set.ftp_filemethod = FTPFILE_MULTICWD; + + /* try to parse ftp url */ + result = ftp_parse_url_path(data); + if(result) { + goto fail; + } + + wildcard->path = strdup(ftp->path); + if(!wildcard->path) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + + /* backup old write_function */ + ftpwc->backup.write_function = data->set.fwrite_func; + /* parsing write function */ + data->set.fwrite_func = Curl_ftp_parselist; + /* backup old file descriptor */ + ftpwc->backup.file_descriptor = data->set.out; + /* let the writefunc callback know the transfer */ + data->set.out = data; + + infof(data, "Wildcard - Parsing started"); + return CURLE_OK; + +fail: + if(ftpwc) { + Curl_ftp_parselist_data_free(&ftpwc->parser); + free(ftpwc); + } + Curl_safefree(wildcard->pattern); + wildcard->dtor = ZERO_NULL; + wildcard->ftpwc = NULL; + return result; +} + +static CURLcode wc_statemach(struct Curl_easy *data) +{ + struct WildcardData * const wildcard = data->wildcard; + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; + + for(;;) { + switch(wildcard->state) { + case CURLWC_INIT: + result = init_wc_data(data); + if(wildcard->state == CURLWC_CLEAN) + /* only listing! */ + return result; + wildcard->state = result ? CURLWC_ERROR : CURLWC_MATCHING; + return result; + + case CURLWC_MATCHING: { + /* In this state is LIST response successfully parsed, so lets restore + previous WRITEFUNCTION callback and WRITEDATA pointer */ + struct ftp_wc *ftpwc = wildcard->ftpwc; + data->set.fwrite_func = ftpwc->backup.write_function; + data->set.out = ftpwc->backup.file_descriptor; + ftpwc->backup.write_function = ZERO_NULL; + ftpwc->backup.file_descriptor = NULL; + wildcard->state = CURLWC_DOWNLOADING; + + if(Curl_ftp_parselist_geterror(ftpwc->parser)) { + /* error found in LIST parsing */ + wildcard->state = CURLWC_CLEAN; + continue; + } + if(wildcard->filelist.size == 0) { + /* no corresponding file */ + wildcard->state = CURLWC_CLEAN; + return CURLE_REMOTE_FILE_NOT_FOUND; + } + continue; + } + + case CURLWC_DOWNLOADING: { + /* filelist has at least one file, lets get first one */ + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct curl_fileinfo *finfo = wildcard->filelist.head->ptr; + struct FTP *ftp = data->req.p.ftp; + + char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename); + if(!tmp_path) + return CURLE_OUT_OF_MEMORY; + + /* switch default ftp->path and tmp_path */ + free(ftp->pathalloc); + ftp->pathalloc = ftp->path = tmp_path; + + infof(data, "Wildcard - START of \"%s\"", finfo->filename); + if(data->set.chunk_bgn) { + long userresponse; + Curl_set_in_callback(data, true); + userresponse = data->set.chunk_bgn( + finfo, data->set.wildcardptr, (int)wildcard->filelist.size); + Curl_set_in_callback(data, false); + switch(userresponse) { + case CURL_CHUNK_BGN_FUNC_SKIP: + infof(data, "Wildcard - \"%s\" skipped by user", + finfo->filename); + wildcard->state = CURLWC_SKIP; + continue; + case CURL_CHUNK_BGN_FUNC_FAIL: + return CURLE_CHUNK_FAILED; + } + } + + if(finfo->filetype != CURLFILETYPE_FILE) { + wildcard->state = CURLWC_SKIP; + continue; + } + + if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE) + ftpc->known_filesize = finfo->size; + + result = ftp_parse_url_path(data); + if(result) + return result; + + /* we don't need the Curl_fileinfo of first file anymore */ + Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); + + if(wildcard->filelist.size == 0) { /* remains only one file to down. */ + wildcard->state = CURLWC_CLEAN; + /* after that will be ftp_do called once again and no transfer + will be done because of CURLWC_CLEAN state */ + return CURLE_OK; + } + return result; + } + + case CURLWC_SKIP: { + if(data->set.chunk_end) { + Curl_set_in_callback(data, true); + data->set.chunk_end(data->set.wildcardptr); + Curl_set_in_callback(data, false); + } + Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); + wildcard->state = (wildcard->filelist.size == 0) ? + CURLWC_CLEAN : CURLWC_DOWNLOADING; + continue; + } + + case CURLWC_CLEAN: { + struct ftp_wc *ftpwc = wildcard->ftpwc; + result = CURLE_OK; + if(ftpwc) + result = Curl_ftp_parselist_geterror(ftpwc->parser); + + wildcard->state = result ? CURLWC_ERROR : CURLWC_DONE; + return result; + } + + case CURLWC_DONE: + case CURLWC_ERROR: + case CURLWC_CLEAR: + if(wildcard->dtor) { + wildcard->dtor(wildcard->ftpwc); + wildcard->ftpwc = NULL; + } + return result; + } + } + /* UNREACHABLE */ +} + +/*********************************************************************** + * + * ftp_do() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (ftp_perform). + * + * The input argument is already checked for validity. + */ +static CURLcode ftp_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + *done = FALSE; /* default to false */ + ftpc->wait_data_conn = FALSE; /* default to no such wait */ + + if(data->state.wildcardmatch) { + result = wc_statemach(data); + if(data->wildcard->state == CURLWC_SKIP || + data->wildcard->state == CURLWC_DONE) { + /* do not call ftp_regular_transfer */ + return CURLE_OK; + } + if(result) /* error, loop or skipping the file */ + return result; + } + else { /* no wildcard FSM needed */ + result = ftp_parse_url_path(data); + if(result) + return result; + } + + result = ftp_regular_transfer(data, done); + + return result; +} + +/*********************************************************************** + * + * ftp_quit() + * + * This should be called before calling sclose() on an ftp control connection + * (not data connections). We should then wait for the response from the + * server before returning. The calling code should then try to close the + * connection. + * + */ +static CURLcode ftp_quit(struct Curl_easy *data, struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + if(conn->proto.ftpc.ctl_valid) { + result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "QUIT"); + if(result) { + failf(data, "Failure sending QUIT command: %s", + curl_easy_strerror(result)); + conn->proto.ftpc.ctl_valid = FALSE; /* mark control connection as bad */ + connclose(conn, "QUIT command failed"); /* mark for connection closure */ + ftp_state(data, FTP_STOP); + return result; + } + + ftp_state(data, FTP_QUIT); + + result = ftp_block_statemach(data, conn); + } + + return result; +} + +/*********************************************************************** + * + * ftp_disconnect() + * + * Disconnect from an FTP server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +static CURLcode ftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. + + ftp_quit() will check the state of ftp->ctl_valid. If it's ok it + will try to send the QUIT command, otherwise it will just return. + */ + if(dead_connection) + ftpc->ctl_valid = FALSE; + + /* The FTP session may or may not have been allocated/setup at this point! */ + (void)ftp_quit(data, conn); /* ignore errors on the QUIT */ + + if(ftpc->entrypath) { + if(data->state.most_recent_ftp_entrypath == ftpc->entrypath) { + data->state.most_recent_ftp_entrypath = NULL; + } + Curl_safefree(ftpc->entrypath); + } + + freedirs(ftpc); + Curl_safefree(ftpc->account); + Curl_safefree(ftpc->alternative_to_user); + Curl_safefree(ftpc->prevpath); + Curl_safefree(ftpc->server_os); + Curl_pp_disconnect(pp); + Curl_sec_end(conn); + return CURLE_OK; +} + +#ifdef _MSC_VER +/* warning C4706: assignment within conditional expression */ +#pragma warning(disable:4706) +#endif + +/*********************************************************************** + * + * ftp_parse_url_path() + * + * Parse the URL path into separate path components. + * + */ +static +CURLcode ftp_parse_url_path(struct Curl_easy *data) +{ + /* the ftp struct is already inited in ftp_connect() */ + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + const char *slashPos = NULL; + const char *fileName = NULL; + CURLcode result = CURLE_OK; + char *rawPath = NULL; /* url-decoded "raw" path */ + size_t pathLen = 0; + + ftpc->ctl_valid = FALSE; + ftpc->cwdfail = FALSE; + + /* url-decode ftp path before further evaluation */ + result = Curl_urldecode(ftp->path, 0, &rawPath, &pathLen, REJECT_CTRL); + if(result) { + failf(data, "path contains control characters"); + return result; + } + + switch(data->set.ftp_filemethod) { + case FTPFILE_NOCWD: /* fastest, but less standard-compliant */ + + 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. + */ + break; + + case FTPFILE_SINGLECWD: + slashPos = strrchr(rawPath, '/'); + if(slashPos) { + /* get path before last slash, except for / */ + size_t dirlen = slashPos - rawPath; + if(dirlen == 0) + dirlen = 1; + + ftpc->dirs = calloc(1, sizeof(ftpc->dirs[0])); + if(!ftpc->dirs) { + free(rawPath); + return CURLE_OUT_OF_MEMORY; + } + + ftpc->dirs[0] = calloc(1, dirlen + 1); + if(!ftpc->dirs[0]) { + free(rawPath); + return CURLE_OUT_OF_MEMORY; + } + + strncpy(ftpc->dirs[0], rawPath, dirlen); + ftpc->dirdepth = 1; /* we consider it to be a single dir */ + fileName = slashPos + 1; /* rest is file name */ + } + else + fileName = rawPath; /* file name only (or empty) */ + break; + + default: /* allow pretty much anything */ + case FTPFILE_MULTICWD: { + /* current position: begin of next path component */ + const char *curPos = rawPath; + + /* number of entries allocated for the 'dirs' array */ + size_t dirAlloc = 0; + const char *str = rawPath; + for(; *str != 0; ++str) + if(*str == '/') + ++dirAlloc; + + if(dirAlloc) { + ftpc->dirs = calloc(dirAlloc, sizeof(ftpc->dirs[0])); + if(!ftpc->dirs) { + free(rawPath); + return CURLE_OUT_OF_MEMORY; + } + + /* parse the URL path into separate path components */ + while((slashPos = strchr(curPos, '/'))) { + size_t compLen = slashPos - curPos; + + /* path starts with a slash: add that as a directory */ + if((compLen == 0) && (ftpc->dirdepth == 0)) + ++compLen; + + /* we skip empty path components, like "x//y" since the FTP command + CWD requires a parameter and a non-existent parameter a) doesn't + work on many servers and b) has no effect on the others. */ + if(compLen > 0) { + char *comp = calloc(1, compLen + 1); + if(!comp) { + free(rawPath); + return CURLE_OUT_OF_MEMORY; + } + strncpy(comp, curPos, compLen); + ftpc->dirs[ftpc->dirdepth++] = comp; + } + curPos = slashPos + 1; + } + } + DEBUGASSERT((size_t)ftpc->dirdepth <= dirAlloc); + fileName = curPos; /* the rest is the file name (or empty) */ + } + break; + } /* switch */ + + if(fileName && *fileName) + ftpc->file = strdup(fileName); + else + ftpc->file = NULL; /* instead of point to a zero byte, + we make it a NULL pointer */ + + if(data->state.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) { + /* We need a file name when uploading. Return error! */ + failf(data, "Uploading to a URL without a file name"); + free(rawPath); + return CURLE_URL_MALFORMAT; + } + + ftpc->cwddone = FALSE; /* default to not done */ + + if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (rawPath[0] == '/')) + ftpc->cwddone = TRUE; /* skip CWD for absolute paths */ + else { /* newly created FTP connections are already in entry path */ + const char *oldPath = conn->bits.reuse ? ftpc->prevpath : ""; + if(oldPath) { + size_t n = pathLen; + if(data->set.ftp_filemethod == FTPFILE_NOCWD) + n = 0; /* CWD to entry for relative paths */ + else + n -= ftpc->file?strlen(ftpc->file):0; + + if((strlen(oldPath) == n) && !strncmp(rawPath, oldPath, n)) { + infof(data, "Request has same path as previous transfer"); + ftpc->cwddone = TRUE; + } + } + } + + free(rawPath); + return CURLE_OK; +} + +/* call this when the DO phase has completed */ +static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected) +{ + struct connectdata *conn = data->conn; + struct FTP *ftp = data->req.p.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if(connected) { + int completed; + CURLcode result = ftp_do_more(data, &completed); + + if(result) { + close_secondarysocket(data, conn); + return result; + } + } + + if(ftp->transfer != PPTRANSFER_BODY) + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + else if(!connected) + /* since we didn't connect now, we want do_more to get called */ + conn->bits.do_more = TRUE; + + ftpc->ctl_valid = TRUE; /* seems good */ + + return CURLE_OK; +} + +/* called from multi.c while DOing */ +static CURLcode ftp_doing(struct Curl_easy *data, + bool *dophase_done) +{ + CURLcode result = ftp_multi_statemach(data, dophase_done); + + if(result) + DEBUGF(infof(data, "DO phase failed")); + else if(*dophase_done) { + result = ftp_dophase_done(data, FALSE /* not connected */); + + DEBUGF(infof(data, "DO phase is complete2")); + } + return result; +} + +/*********************************************************************** + * + * ftp_regular_transfer() + * + * The input argument is already checked for validity. + * + * Performs all commands done before a regular transfer between a local and a + * remote host. + * + * ftp->ctl_valid starts out as FALSE, and gets set to TRUE if we reach the + * ftp_done() function without finding any major problem. + */ +static +CURLcode ftp_regular_transfer(struct Curl_easy *data, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + bool connected = FALSE; + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + data->req.size = -1; /* make sure this is unknown at this point */ + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, -1); + Curl_pgrsSetDownloadSize(data, -1); + + ftpc->ctl_valid = TRUE; /* starts good */ + + result = ftp_perform(data, + &connected, /* have we connected after PASV/PORT */ + dophase_done); /* all commands in the DO-phase done? */ + + if(!result) { + + if(!*dophase_done) + /* the DO phase has not completed yet */ + return CURLE_OK; + + result = ftp_dophase_done(data, connected); + + if(result) + return result; + } + else + freedirs(ftpc); + + return result; +} + +static CURLcode ftp_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + char *type; + struct FTP *ftp; + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + ftp = calloc(1, sizeof(struct FTP)); + if(!ftp) + return CURLE_OUT_OF_MEMORY; + + /* clone connection related data that is FTP specific */ + if(data->set.str[STRING_FTP_ACCOUNT]) { + ftpc->account = strdup(data->set.str[STRING_FTP_ACCOUNT]); + if(!ftpc->account) { + free(ftp); + return CURLE_OUT_OF_MEMORY; + } + } + if(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]) { + ftpc->alternative_to_user = + strdup(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); + if(!ftpc->alternative_to_user) { + Curl_safefree(ftpc->account); + free(ftp); + return CURLE_OUT_OF_MEMORY; + } + } + data->req.p.ftp = ftp; + + ftp->path = &data->state.up.path[1]; /* don't include the initial slash */ + + /* FTP URLs support an extension like ";type=<typecode>" that + * we'll try to get now! */ + type = strstr(ftp->path, ";type="); + + if(!type) + type = strstr(conn->host.rawalloc, ";type="); + + if(type) { + char command; + *type = 0; /* it was in the middle of the hostname */ + command = Curl_raw_toupper(type[6]); + + switch(command) { + case 'A': /* ASCII mode */ + data->state.prefer_ascii = TRUE; + break; + + case 'D': /* directory mode */ + data->state.list_only = TRUE; + break; + + case 'I': /* binary mode */ + default: + /* switch off ASCII */ + data->state.prefer_ascii = FALSE; + break; + } + } + + /* get some initial data into the ftp struct */ + ftp->transfer = PPTRANSFER_BODY; + ftp->downloadsize = 0; + ftpc->known_filesize = -1; /* unknown size for now */ + ftpc->use_ssl = data->set.use_ssl; + ftpc->ccc = data->set.ftp_ccc; + + return result; +} + +#endif /* CURL_DISABLE_FTP */ diff --git a/Utilities/cmcurl/lib/ftp.h b/Utilities/cmcurl/lib/ftp.h new file mode 100644 index 0000000..977fc88 --- /dev/null +++ b/Utilities/cmcurl/lib/ftp.h @@ -0,0 +1,167 @@ +#ifndef HEADER_CURL_FTP_H +#define HEADER_CURL_FTP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "pingpong.h" + +#ifndef CURL_DISABLE_FTP +extern const struct Curl_handler Curl_handler_ftp; + +#ifdef USE_SSL +extern const struct Curl_handler Curl_handler_ftps; +#endif + +CURLcode Curl_GetFTPResponse(struct Curl_easy *data, ssize_t *nread, + int *ftpcode); +#endif /* CURL_DISABLE_FTP */ + +/**************************************************************************** + * FTP unique setup + ***************************************************************************/ +enum { + FTP_STOP, /* do nothing state, stops the state machine */ + FTP_WAIT220, /* waiting for the initial 220 response immediately after + a connect */ + FTP_AUTH, + FTP_USER, + FTP_PASS, + FTP_ACCT, + FTP_PBSZ, + FTP_PROT, + FTP_CCC, + FTP_PWD, + FTP_SYST, + FTP_NAMEFMT, + FTP_QUOTE, /* waiting for a response to a command sent in a quote list */ + FTP_RETR_PREQUOTE, + FTP_STOR_PREQUOTE, + FTP_POSTQUOTE, + FTP_CWD, /* change dir */ + FTP_MKD, /* if the dir didn't exist */ + FTP_MDTM, /* to figure out the datestamp */ + FTP_TYPE, /* to set type when doing a head-like request */ + FTP_LIST_TYPE, /* set type when about to do a dir list */ + FTP_RETR_TYPE, /* set type when about to RETR a file */ + FTP_STOR_TYPE, /* set type when about to STOR a file */ + FTP_SIZE, /* get the remote file's size for head-like request */ + FTP_RETR_SIZE, /* get the remote file's size for RETR */ + FTP_STOR_SIZE, /* get the size for STOR */ + FTP_REST, /* when used to check if the server supports it in head-like */ + FTP_RETR_REST, /* when asking for "resume" in for RETR */ + FTP_PORT, /* generic state for PORT, LPRT and EPRT, check count1 */ + FTP_PRET, /* generic state for PRET RETR, PRET STOR and PRET LIST/NLST */ + FTP_PASV, /* generic state for PASV and EPSV, check count1 */ + FTP_LIST, /* generic state for LIST, NLST or a custom list command */ + FTP_RETR, + FTP_STOR, /* generic state for STOR and APPE */ + FTP_QUIT, + FTP_LAST /* never used */ +}; +typedef unsigned char ftpstate; /* use the enum values */ + +struct ftp_parselist_data; /* defined later in ftplistparser.c */ + +struct ftp_wc { + struct ftp_parselist_data *parser; + + struct { + curl_write_callback write_function; + FILE *file_descriptor; + } backup; +}; + +typedef enum { + FTPFILE_MULTICWD = 1, /* as defined by RFC1738 */ + FTPFILE_NOCWD = 2, /* use SIZE / RETR / STOR on the full path */ + FTPFILE_SINGLECWD = 3 /* make one CWD, then SIZE / RETR / STOR on the + file */ +} curl_ftpfile; + +/* This FTP struct is used in the Curl_easy. All FTP data that is + connection-oriented must be in FTP_conn to properly deal with the fact that + perhaps the Curl_easy is changed between the times the connection is + used. */ +struct FTP { + char *path; /* points to the urlpieces struct field */ + char *pathalloc; /* if non-NULL a pointer to an allocated path */ + + /* transfer a file/body or not, done as a typedefed enum just to make + debuggers display the full symbol and not just the numerical value */ + curl_pp_transfer transfer; + curl_off_t downloadsize; +}; + + +/* ftp_conn is used for struct connection-oriented data in the connectdata + struct */ +struct ftp_conn { + struct pingpong pp; + char *account; + char *alternative_to_user; + char *entrypath; /* the PWD reply when we logged on */ + char *file; /* url-decoded file name (or path) */ + char **dirs; /* realloc()ed array for path components */ + char *newhost; + char *prevpath; /* url-decoded conn->path from the previous transfer */ + char transfertype; /* set by ftp_transfertype for use by Curl_client_write()a + and others (A/I or zero) */ + curl_off_t retr_size_saved; /* Size of retrieved file saved */ + char *server_os; /* The target server operating system. */ + curl_off_t known_filesize; /* file size is different from -1, if wildcard + LIST parsing was done and wc_statemach set + it */ + int dirdepth; /* number of entries used in the 'dirs' array */ + int cwdcount; /* number of CWD commands issued */ + int count1; /* general purpose counter for the state machine */ + int count2; /* general purpose counter for the state machine */ + int count3; /* general purpose counter for the state machine */ + /* newhost is the (allocated) IP addr or host name to connect the data + connection to */ + unsigned short newport; + ftpstate state; /* always use ftp.c:state() to change state! */ + ftpstate state_saved; /* transfer type saved to be reloaded after data + connection is established */ + unsigned char use_ssl; /* if AUTH TLS is to be attempted etc, for FTP or + IMAP or POP3 or others! (type: curl_usessl)*/ + unsigned char ccc; /* ccc level for this connection */ + BIT(ftp_trying_alternative); + BIT(dont_check); /* Set to TRUE to prevent the final (post-transfer) + file size and 226/250 status check. It should still + read the line, just ignore the result. */ + BIT(ctl_valid); /* Tells Curl_ftp_quit() whether or not to do anything. If + the connection has timed out or been closed, this + should be FALSE when it gets to Curl_ftp_quit() */ + BIT(cwddone); /* if it has been determined that the proper CWD combo + already has been done */ + BIT(cwdfail); /* set TRUE if a CWD command fails, as then we must prevent + caching the current directory */ + BIT(wait_data_conn); /* this is set TRUE if data connection is waited */ +}; + +#define DEFAULT_ACCEPT_TIMEOUT 60000 /* milliseconds == one minute */ + +#endif /* HEADER_CURL_FTP_H */ diff --git a/Utilities/cmcurl/lib/ftplistparser.c b/Utilities/cmcurl/lib/ftplistparser.c new file mode 100644 index 0000000..82f1ea0 --- /dev/null +++ b/Utilities/cmcurl/lib/ftplistparser.c @@ -0,0 +1,1041 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/** + * Now implemented: + * + * 1) Unix version 1 + * drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog + * 2) Unix version 2 + * drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog + * 3) Unix version 3 + * drwxr-xr-x 1 1 1 512 Jan 29 23:32 prog + * 4) Unix symlink + * lrwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog -> prog2000 + * 5) DOS style + * 01-29-97 11:32PM <DIR> prog + */ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_FTP + +#include <curl/curl.h> + +#include "urldata.h" +#include "fileinfo.h" +#include "llist.h" +#include "strtoofft.h" +#include "ftp.h" +#include "ftplistparser.h" +#include "curl_fnmatch.h" +#include "curl_memory.h" +#include "multiif.h" +/* The last #include file should be: */ +#include "memdebug.h" + +typedef enum { + PL_UNIX_TOTALSIZE = 0, + PL_UNIX_FILETYPE, + PL_UNIX_PERMISSION, + PL_UNIX_HLINKS, + PL_UNIX_USER, + PL_UNIX_GROUP, + PL_UNIX_SIZE, + PL_UNIX_TIME, + PL_UNIX_FILENAME, + PL_UNIX_SYMLINK +} pl_unix_mainstate; + +typedef union { + enum { + PL_UNIX_TOTALSIZE_INIT = 0, + PL_UNIX_TOTALSIZE_READING + } total_dirsize; + + enum { + PL_UNIX_HLINKS_PRESPACE = 0, + PL_UNIX_HLINKS_NUMBER + } hlinks; + + enum { + PL_UNIX_USER_PRESPACE = 0, + PL_UNIX_USER_PARSING + } user; + + enum { + PL_UNIX_GROUP_PRESPACE = 0, + PL_UNIX_GROUP_NAME + } group; + + enum { + PL_UNIX_SIZE_PRESPACE = 0, + PL_UNIX_SIZE_NUMBER + } size; + + enum { + PL_UNIX_TIME_PREPART1 = 0, + PL_UNIX_TIME_PART1, + PL_UNIX_TIME_PREPART2, + PL_UNIX_TIME_PART2, + PL_UNIX_TIME_PREPART3, + PL_UNIX_TIME_PART3 + } time; + + enum { + PL_UNIX_FILENAME_PRESPACE = 0, + PL_UNIX_FILENAME_NAME, + PL_UNIX_FILENAME_WINDOWSEOL + } filename; + + enum { + PL_UNIX_SYMLINK_PRESPACE = 0, + PL_UNIX_SYMLINK_NAME, + PL_UNIX_SYMLINK_PRETARGET1, + PL_UNIX_SYMLINK_PRETARGET2, + PL_UNIX_SYMLINK_PRETARGET3, + PL_UNIX_SYMLINK_PRETARGET4, + PL_UNIX_SYMLINK_TARGET, + PL_UNIX_SYMLINK_WINDOWSEOL + } symlink; +} pl_unix_substate; + +typedef enum { + PL_WINNT_DATE = 0, + PL_WINNT_TIME, + PL_WINNT_DIRORSIZE, + PL_WINNT_FILENAME +} pl_winNT_mainstate; + +typedef union { + enum { + PL_WINNT_TIME_PRESPACE = 0, + PL_WINNT_TIME_TIME + } time; + enum { + PL_WINNT_DIRORSIZE_PRESPACE = 0, + PL_WINNT_DIRORSIZE_CONTENT + } dirorsize; + enum { + PL_WINNT_FILENAME_PRESPACE = 0, + PL_WINNT_FILENAME_CONTENT, + PL_WINNT_FILENAME_WINEOL + } filename; +} pl_winNT_substate; + +/* This struct is used in wildcard downloading - for parsing LIST response */ +struct ftp_parselist_data { + enum { + OS_TYPE_UNKNOWN = 0, + OS_TYPE_UNIX, + OS_TYPE_WIN_NT + } os_type; + + union { + struct { + pl_unix_mainstate main; + pl_unix_substate sub; + } UNIX; + + struct { + pl_winNT_mainstate main; + pl_winNT_substate sub; + } NT; + } state; + + CURLcode error; + struct fileinfo *file_data; + unsigned int item_length; + size_t item_offset; + struct { + size_t filename; + size_t user; + size_t group; + size_t time; + size_t perm; + size_t symlink_target; + } offsets; +}; + +static void fileinfo_dtor(void *user, void *element) +{ + (void)user; + Curl_fileinfo_cleanup(element); +} + +CURLcode Curl_wildcard_init(struct WildcardData *wc) +{ + Curl_llist_init(&wc->filelist, fileinfo_dtor); + wc->state = CURLWC_INIT; + + return CURLE_OK; +} + +void Curl_wildcard_dtor(struct WildcardData **wcp) +{ + struct WildcardData *wc = *wcp; + if(!wc) + return; + + if(wc->dtor) { + wc->dtor(wc->ftpwc); + wc->dtor = ZERO_NULL; + wc->ftpwc = NULL; + } + DEBUGASSERT(wc->ftpwc == NULL); + + Curl_llist_destroy(&wc->filelist, NULL); + free(wc->path); + wc->path = NULL; + free(wc->pattern); + wc->pattern = NULL; + wc->state = CURLWC_INIT; + free(wc); + *wcp = NULL; +} + +struct ftp_parselist_data *Curl_ftp_parselist_data_alloc(void) +{ + return calloc(1, sizeof(struct ftp_parselist_data)); +} + + +void Curl_ftp_parselist_data_free(struct ftp_parselist_data **parserp) +{ + struct ftp_parselist_data *parser = *parserp; + if(parser) + Curl_fileinfo_cleanup(parser->file_data); + free(parser); + *parserp = NULL; +} + + +CURLcode Curl_ftp_parselist_geterror(struct ftp_parselist_data *pl_data) +{ + return pl_data->error; +} + + +#define FTP_LP_MALFORMATED_PERM 0x01000000 + +static unsigned int ftp_pl_get_permission(const char *str) +{ + unsigned int permissions = 0; + /* USER */ + if(str[0] == 'r') + permissions |= 1 << 8; + else if(str[0] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + if(str[1] == 'w') + permissions |= 1 << 7; + else if(str[1] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + + if(str[2] == 'x') + permissions |= 1 << 6; + else if(str[2] == 's') { + permissions |= 1 << 6; + permissions |= 1 << 11; + } + else if(str[2] == 'S') + permissions |= 1 << 11; + else if(str[2] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + /* GROUP */ + if(str[3] == 'r') + permissions |= 1 << 5; + else if(str[3] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + if(str[4] == 'w') + permissions |= 1 << 4; + else if(str[4] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + if(str[5] == 'x') + permissions |= 1 << 3; + else if(str[5] == 's') { + permissions |= 1 << 3; + permissions |= 1 << 10; + } + else if(str[5] == 'S') + permissions |= 1 << 10; + else if(str[5] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + /* others */ + if(str[6] == 'r') + permissions |= 1 << 2; + else if(str[6] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + if(str[7] == 'w') + permissions |= 1 << 1; + else if(str[7] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + if(str[8] == 'x') + permissions |= 1; + else if(str[8] == 't') { + permissions |= 1; + permissions |= 1 << 9; + } + else if(str[8] == 'T') + permissions |= 1 << 9; + else if(str[8] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + + return permissions; +} + +static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, + struct fileinfo *infop) +{ + curl_fnmatch_callback compare; + struct WildcardData *wc = data->wildcard; + struct ftp_wc *ftpwc = wc->ftpwc; + struct Curl_llist *llist = &wc->filelist; + struct ftp_parselist_data *parser = ftpwc->parser; + bool add = TRUE; + struct curl_fileinfo *finfo = &infop->info; + + /* set the finfo pointers */ + char *str = Curl_dyn_ptr(&infop->buf); + finfo->filename = str + parser->offsets.filename; + finfo->strings.group = parser->offsets.group ? + str + parser->offsets.group : NULL; + finfo->strings.perm = parser->offsets.perm ? + str + parser->offsets.perm : NULL; + finfo->strings.target = parser->offsets.symlink_target ? + str + parser->offsets.symlink_target : NULL; + finfo->strings.time = str + parser->offsets.time; + finfo->strings.user = parser->offsets.user ? + str + parser->offsets.user : NULL; + + /* get correct fnmatch callback */ + compare = data->set.fnmatch; + if(!compare) + 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, " -> "))) { + add = FALSE; + } + } + else { + add = FALSE; + } + Curl_set_in_callback(data, false); + + if(add) { + Curl_llist_insert_next(llist, llist->tail, finfo, &infop->list); + } + else { + Curl_fileinfo_cleanup(infop); + } + + ftpwc->parser->file_data = NULL; + return CURLE_OK; +} + +#define MAX_FTPLIST_BUFFER 10000 /* arbitrarily set */ + +size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, + void *connptr) +{ + size_t bufflen = size*nmemb; + struct Curl_easy *data = (struct Curl_easy *)connptr; + struct ftp_wc *ftpwc = data->wildcard->ftpwc; + struct ftp_parselist_data *parser = ftpwc->parser; + size_t i = 0; + CURLcode result; + size_t retsize = bufflen; + + if(parser->error) { /* error in previous call */ + /* scenario: + * 1. call => OK.. + * 2. call => OUT_OF_MEMORY (or other error) + * 3. (last) call => is skipped RIGHT HERE and the error is handled later + * in wc_statemach() + */ + goto fail; + } + + if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) { + /* considering info about FILE response format */ + parser->os_type = ISDIGIT(buffer[0]) ? OS_TYPE_WIN_NT : OS_TYPE_UNIX; + } + + while(i < bufflen) { /* FSM */ + char *mem; + size_t len; /* number of bytes of data in the dynbuf */ + char c = buffer[i]; + struct fileinfo *infop; + struct curl_fileinfo *finfo; + if(!parser->file_data) { /* tmp file data is not allocated yet */ + parser->file_data = Curl_fileinfo_alloc(); + if(!parser->file_data) { + parser->error = CURLE_OUT_OF_MEMORY; + goto fail; + } + parser->item_offset = 0; + parser->item_length = 0; + Curl_dyn_init(&parser->file_data->buf, MAX_FTPLIST_BUFFER); + } + + infop = parser->file_data; + finfo = &infop->info; + + if(Curl_dyn_addn(&infop->buf, &c, 1)) { + parser->error = CURLE_OUT_OF_MEMORY; + goto fail; + } + len = Curl_dyn_len(&infop->buf); + mem = Curl_dyn_ptr(&infop->buf); + + switch(parser->os_type) { + case OS_TYPE_UNIX: + switch(parser->state.UNIX.main) { + case PL_UNIX_TOTALSIZE: + switch(parser->state.UNIX.sub.total_dirsize) { + case PL_UNIX_TOTALSIZE_INIT: + if(c == 't') { + parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING; + parser->item_length++; + } + else { + parser->state.UNIX.main = PL_UNIX_FILETYPE; + /* start FSM again not considering size of directory */ + Curl_dyn_reset(&infop->buf); + continue; + } + break; + case PL_UNIX_TOTALSIZE_READING: + parser->item_length++; + if(c == '\r') { + parser->item_length--; + Curl_dyn_setlen(&infop->buf, --len); + } + else if(c == '\n') { + mem[parser->item_length - 1] = 0; + if(!strncmp("total ", mem, 6)) { + char *endptr = mem + 6; + /* here we can deal with directory size, pass the leading + whitespace and then the digits */ + while(ISBLANK(*endptr)) + endptr++; + while(ISDIGIT(*endptr)) + endptr++; + if(*endptr) { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + parser->state.UNIX.main = PL_UNIX_FILETYPE; + Curl_dyn_reset(&infop->buf); + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + } + break; + } + break; + case PL_UNIX_FILETYPE: + switch(c) { + case '-': + finfo->filetype = CURLFILETYPE_FILE; + break; + case 'd': + finfo->filetype = CURLFILETYPE_DIRECTORY; + break; + case 'l': + finfo->filetype = CURLFILETYPE_SYMLINK; + break; + case 'p': + finfo->filetype = CURLFILETYPE_NAMEDPIPE; + break; + case 's': + finfo->filetype = CURLFILETYPE_SOCKET; + break; + case 'c': + finfo->filetype = CURLFILETYPE_DEVICE_CHAR; + break; + case 'b': + finfo->filetype = CURLFILETYPE_DEVICE_BLOCK; + break; + case 'D': + finfo->filetype = CURLFILETYPE_DOOR; + break; + default: + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + parser->state.UNIX.main = PL_UNIX_PERMISSION; + parser->item_length = 0; + parser->item_offset = 1; + break; + case PL_UNIX_PERMISSION: + parser->item_length++; + if(parser->item_length <= 9) { + if(!strchr("rwx-tTsS", c)) { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + } + else if(parser->item_length == 10) { + unsigned int perm; + if(c != ' ') { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + mem[10] = 0; /* terminate permissions */ + perm = ftp_pl_get_permission(mem + parser->item_offset); + if(perm & FTP_LP_MALFORMATED_PERM) { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_PERM; + parser->file_data->info.perm = perm; + parser->offsets.perm = parser->item_offset; + + parser->item_length = 0; + parser->state.UNIX.main = PL_UNIX_HLINKS; + parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE; + } + break; + case PL_UNIX_HLINKS: + switch(parser->state.UNIX.sub.hlinks) { + case PL_UNIX_HLINKS_PRESPACE: + if(c != ' ') { + if(ISDIGIT(c)) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER; + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + } + break; + case PL_UNIX_HLINKS_NUMBER: + parser->item_length ++; + if(c == ' ') { + char *p; + long int hlinks; + mem[parser->item_offset + parser->item_length - 1] = 0; + hlinks = strtol(mem + parser->item_offset, &p, 10); + if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) { + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT; + parser->file_data->info.hardlinks = hlinks; + } + parser->item_length = 0; + parser->item_offset = 0; + parser->state.UNIX.main = PL_UNIX_USER; + parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE; + } + else if(!ISDIGIT(c)) { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + } + break; + case PL_UNIX_USER: + switch(parser->state.UNIX.sub.user) { + case PL_UNIX_USER_PRESPACE: + if(c != ' ') { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING; + } + break; + case PL_UNIX_USER_PARSING: + parser->item_length++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.user = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_GROUP; + parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE; + parser->item_offset = 0; + parser->item_length = 0; + } + break; + } + break; + case PL_UNIX_GROUP: + switch(parser->state.UNIX.sub.group) { + case PL_UNIX_GROUP_PRESPACE: + if(c != ' ') { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME; + } + break; + case PL_UNIX_GROUP_NAME: + parser->item_length++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.group = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_SIZE; + parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE; + parser->item_offset = 0; + parser->item_length = 0; + } + break; + } + break; + case PL_UNIX_SIZE: + switch(parser->state.UNIX.sub.size) { + case PL_UNIX_SIZE_PRESPACE: + if(c != ' ') { + if(ISDIGIT(c)) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER; + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + } + break; + case PL_UNIX_SIZE_NUMBER: + parser->item_length++; + if(c == ' ') { + char *p; + curl_off_t fsize; + mem[parser->item_offset + parser->item_length - 1] = 0; + if(!curlx_strtoofft(mem + parser->item_offset, + &p, 10, &fsize)) { + if(p[0] == '\0' && fsize != CURL_OFF_T_MAX && + fsize != CURL_OFF_T_MIN) { + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE; + parser->file_data->info.size = fsize; + } + parser->item_length = 0; + parser->item_offset = 0; + parser->state.UNIX.main = PL_UNIX_TIME; + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1; + } + } + else if(!ISDIGIT(c)) { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + } + break; + case PL_UNIX_TIME: + switch(parser->state.UNIX.sub.time) { + case PL_UNIX_TIME_PREPART1: + if(c != ' ') { + if(ISALNUM(c)) { + parser->item_offset = len -1; + parser->item_length = 1; + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1; + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + } + break; + case PL_UNIX_TIME_PART1: + parser->item_length++; + if(c == ' ') { + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2; + } + else if(!ISALNUM(c) && c != '.') { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + case PL_UNIX_TIME_PREPART2: + parser->item_length++; + if(c != ' ') { + if(ISALNUM(c)) { + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2; + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + } + break; + case PL_UNIX_TIME_PART2: + parser->item_length++; + if(c == ' ') { + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3; + } + else if(!ISALNUM(c) && c != '.') { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + case PL_UNIX_TIME_PREPART3: + parser->item_length++; + if(c != ' ') { + if(ISALNUM(c)) { + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3; + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + } + break; + case PL_UNIX_TIME_PART3: + parser->item_length++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length -1] = 0; + parser->offsets.time = parser->item_offset; + /* + if(ftp_pl_gettime(parser, finfo->mem + parser->item_offset)) { + parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME; + } + */ + if(finfo->filetype == CURLFILETYPE_SYMLINK) { + parser->state.UNIX.main = PL_UNIX_SYMLINK; + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE; + } + else { + parser->state.UNIX.main = PL_UNIX_FILENAME; + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE; + } + } + else if(!ISALNUM(c) && c != '.' && c != ':') { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + } + break; + case PL_UNIX_FILENAME: + switch(parser->state.UNIX.sub.filename) { + case PL_UNIX_FILENAME_PRESPACE: + if(c != ' ') { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME; + } + break; + case PL_UNIX_FILENAME_NAME: + parser->item_length++; + if(c == '\r') { + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL; + } + else if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.filename = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_FILETYPE; + result = ftp_pl_insert_finfo(data, infop); + if(result) { + parser->error = result; + goto fail; + } + } + break; + case PL_UNIX_FILENAME_WINDOWSEOL: + if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.filename = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_FILETYPE; + result = ftp_pl_insert_finfo(data, infop); + if(result) { + parser->error = result; + goto fail; + } + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + } + break; + case PL_UNIX_SYMLINK: + switch(parser->state.UNIX.sub.symlink) { + case PL_UNIX_SYMLINK_PRESPACE: + if(c != ' ') { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + } + break; + case PL_UNIX_SYMLINK_NAME: + parser->item_length++; + if(c == ' ') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1; + } + else if(c == '\r' || c == '\n') { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + case PL_UNIX_SYMLINK_PRETARGET1: + parser->item_length++; + if(c == '-') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2; + } + else if(c == '\r' || c == '\n') { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + else { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + } + break; + case PL_UNIX_SYMLINK_PRETARGET2: + parser->item_length++; + if(c == '>') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3; + } + else if(c == '\r' || c == '\n') { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + else { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + } + break; + case PL_UNIX_SYMLINK_PRETARGET3: + parser->item_length++; + if(c == ' ') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4; + /* now place where is symlink following */ + mem[parser->item_offset + parser->item_length - 4] = 0; + parser->offsets.filename = parser->item_offset; + parser->item_length = 0; + parser->item_offset = 0; + } + else if(c == '\r' || c == '\n') { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + else { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + } + break; + case PL_UNIX_SYMLINK_PRETARGET4: + if(c != '\r' && c != '\n') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET; + parser->item_offset = len - 1; + parser->item_length = 1; + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + case PL_UNIX_SYMLINK_TARGET: + parser->item_length++; + if(c == '\r') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL; + } + else if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.symlink_target = parser->item_offset; + result = ftp_pl_insert_finfo(data, infop); + if(result) { + parser->error = result; + goto fail; + } + parser->state.UNIX.main = PL_UNIX_FILETYPE; + } + break; + case PL_UNIX_SYMLINK_WINDOWSEOL: + if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.symlink_target = parser->item_offset; + result = ftp_pl_insert_finfo(data, infop); + if(result) { + parser->error = result; + goto fail; + } + parser->state.UNIX.main = PL_UNIX_FILETYPE; + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + } + break; + } + break; + case OS_TYPE_WIN_NT: + switch(parser->state.NT.main) { + case PL_WINNT_DATE: + parser->item_length++; + if(parser->item_length < 9) { + if(!strchr("0123456789-", c)) { /* only simple control */ + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + } + else if(parser->item_length == 9) { + if(c == ' ') { + parser->state.NT.main = PL_WINNT_TIME; + parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE; + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + case PL_WINNT_TIME: + parser->item_length++; + switch(parser->state.NT.sub.time) { + case PL_WINNT_TIME_PRESPACE: + if(!ISBLANK(c)) { + parser->state.NT.sub.time = PL_WINNT_TIME_TIME; + } + break; + case PL_WINNT_TIME_TIME: + if(c == ' ') { + parser->offsets.time = parser->item_offset; + mem[parser->item_offset + parser->item_length -1] = 0; + parser->state.NT.main = PL_WINNT_DIRORSIZE; + parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE; + parser->item_length = 0; + } + else if(!strchr("APM0123456789:", c)) { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + } + break; + case PL_WINNT_DIRORSIZE: + switch(parser->state.NT.sub.dirorsize) { + case PL_WINNT_DIRORSIZE_PRESPACE: + if(c != ' ') { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT; + } + break; + case PL_WINNT_DIRORSIZE_CONTENT: + parser->item_length ++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length - 1] = 0; + if(strcmp("<DIR>", mem + parser->item_offset) == 0) { + finfo->filetype = CURLFILETYPE_DIRECTORY; + finfo->size = 0; + } + else { + char *endptr; + if(curlx_strtoofft(mem + + parser->item_offset, + &endptr, 10, &finfo->size)) { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + /* correct file type */ + parser->file_data->info.filetype = CURLFILETYPE_FILE; + } + + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE; + parser->item_length = 0; + parser->state.NT.main = PL_WINNT_FILENAME; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + break; + } + break; + case PL_WINNT_FILENAME: + switch(parser->state.NT.sub.filename) { + case PL_WINNT_FILENAME_PRESPACE: + if(c != ' ') { + parser->item_offset = len -1; + parser->item_length = 1; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT; + } + break; + case PL_WINNT_FILENAME_CONTENT: + parser->item_length++; + if(c == '\r') { + parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL; + mem[len - 1] = 0; + } + else if(c == '\n') { + parser->offsets.filename = parser->item_offset; + mem[len - 1] = 0; + result = ftp_pl_insert_finfo(data, infop); + if(result) { + parser->error = result; + goto fail; + } + parser->state.NT.main = PL_WINNT_DATE; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + break; + case PL_WINNT_FILENAME_WINEOL: + if(c == '\n') { + parser->offsets.filename = parser->item_offset; + result = ftp_pl_insert_finfo(data, infop); + if(result) { + parser->error = result; + goto fail; + } + parser->state.NT.main = PL_WINNT_DATE; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + else { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; + } + break; + } + break; + } + break; + default: + retsize = bufflen + 1; + goto fail; + } + + i++; + } + return retsize; + +fail: + + /* Clean up any allocated memory. */ + if(parser->file_data) { + Curl_fileinfo_cleanup(parser->file_data); + parser->file_data = NULL; + } + + return retsize; +} + +#endif /* CURL_DISABLE_FTP */ diff --git a/Utilities/cmcurl/lib/ftplistparser.h b/Utilities/cmcurl/lib/ftplistparser.h new file mode 100644 index 0000000..5ba1f6a --- /dev/null +++ b/Utilities/cmcurl/lib/ftplistparser.h @@ -0,0 +1,77 @@ +#ifndef HEADER_CURL_FTPLISTPARSER_H +#define HEADER_CURL_FTPLISTPARSER_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifndef CURL_DISABLE_FTP + +/* WRITEFUNCTION callback for parsing LIST responses */ +size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, + void *connptr); + +struct ftp_parselist_data; /* defined inside ftplibparser.c */ + +CURLcode Curl_ftp_parselist_geterror(struct ftp_parselist_data *pl_data); + +struct ftp_parselist_data *Curl_ftp_parselist_data_alloc(void); + +void Curl_ftp_parselist_data_free(struct ftp_parselist_data **pl_data); + +/* list of wildcard process states */ +typedef enum { + CURLWC_CLEAR = 0, + CURLWC_INIT = 1, + CURLWC_MATCHING, /* library is trying to get list of addresses for + downloading */ + CURLWC_DOWNLOADING, + CURLWC_CLEAN, /* deallocate resources and reset settings */ + CURLWC_SKIP, /* skip over concrete file */ + CURLWC_ERROR, /* error cases */ + CURLWC_DONE /* if is wildcard->state == CURLWC_DONE wildcard loop + will end */ +} wildcard_states; + +typedef void (*wildcard_dtor)(void *ptr); + +/* struct keeping information about wildcard download process */ +struct WildcardData { + char *path; /* path to the directory, where we trying wildcard-match */ + char *pattern; /* wildcard pattern */ + struct Curl_llist filelist; /* llist with struct Curl_fileinfo */ + struct ftp_wc *ftpwc; /* pointer to FTP wildcard data */ + wildcard_dtor dtor; + unsigned char state; /* wildcard_states */ +}; + +CURLcode Curl_wildcard_init(struct WildcardData *wc); +void Curl_wildcard_dtor(struct WildcardData **wcp); + +struct Curl_easy; + +#else +/* FTP is disabled */ +#define Curl_wildcard_dtor(x) +#endif /* CURL_DISABLE_FTP */ +#endif /* HEADER_CURL_FTPLISTPARSER_H */ diff --git a/Utilities/cmcurl/lib/functypes.h b/Utilities/cmcurl/lib/functypes.h new file mode 100644 index 0000000..ea66d32 --- /dev/null +++ b/Utilities/cmcurl/lib/functypes.h @@ -0,0 +1,115 @@ +#ifndef HEADER_CURL_FUNCTYPES_H +#define HEADER_CURL_FUNCTYPES_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +/* defaults: + + ssize_t recv(int, void *, size_t, int); + ssize_t send(int, const void *, size_t, int); + + If other argument or return types are needed: + + 1. For systems that run configure or cmake, the alternatives are provided + here. + 2. For systems with config-*.h files, define them there. +*/ + +#ifdef _WIN32 +/* int recv(SOCKET, char *, int, int) */ +#define RECV_TYPE_ARG1 SOCKET +#define RECV_TYPE_ARG2 char * +#define RECV_TYPE_ARG3 int +#define RECV_TYPE_RETV int + +/* int send(SOCKET, const char *, int, int); */ +#define SEND_TYPE_ARG1 SOCKET +#define SEND_TYPE_ARG2 char * +#define SEND_TYPE_ARG3 int +#define SEND_TYPE_RETV int + +#elif defined(__AMIGA__) /* Any AmigaOS flavour */ + +/* long recv(long, char *, long, long); */ +#define RECV_TYPE_ARG1 long +#define RECV_TYPE_ARG2 char * +#define RECV_TYPE_ARG3 long +#define RECV_TYPE_ARG4 long +#define RECV_TYPE_RETV long + +/* int send(int, const char *, int, int); */ +#define SEND_TYPE_ARG1 int +#define SEND_TYPE_ARG2 char * +#define SEND_TYPE_ARG3 int +#define SEND_TYPE_RETV int +#endif + + +#ifndef RECV_TYPE_ARG1 +#define RECV_TYPE_ARG1 int +#endif + +#ifndef RECV_TYPE_ARG2 +#define RECV_TYPE_ARG2 void * +#endif + +#ifndef RECV_TYPE_ARG3 +#define RECV_TYPE_ARG3 size_t +#endif + +#ifndef RECV_TYPE_ARG4 +#define RECV_TYPE_ARG4 int +#endif + +#ifndef RECV_TYPE_RETV +#define RECV_TYPE_RETV ssize_t +#endif + +#ifndef SEND_QUAL_ARG2 +#define SEND_QUAL_ARG2 const +#endif + +#ifndef SEND_TYPE_ARG1 +#define SEND_TYPE_ARG1 int +#endif + +#ifndef SEND_TYPE_ARG2 +#define SEND_TYPE_ARG2 void * +#endif + +#ifndef SEND_TYPE_ARG3 +#define SEND_TYPE_ARG3 size_t +#endif + +#ifndef SEND_TYPE_ARG4 +#define SEND_TYPE_ARG4 int +#endif + +#ifndef SEND_TYPE_RETV +#define SEND_TYPE_RETV ssize_t +#endif + +#endif /* HEADER_CURL_FUNCTYPES_H */ diff --git a/Utilities/cmcurl/lib/getenv.c b/Utilities/cmcurl/lib/getenv.c new file mode 100644 index 0000000..48ee972 --- /dev/null +++ b/Utilities/cmcurl/lib/getenv.c @@ -0,0 +1,80 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> +#include "curl_memory.h" + +#include "memdebug.h" + +static char *GetEnv(const char *variable) +{ +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_APP) || \ + defined(__ORBIS__) || defined(__PROSPERO__) /* PlayStation 4 and 5 */ + (void)variable; + return NULL; +#elif defined(_WIN32) + /* This uses Windows API instead of C runtime getenv() to get the environment + variable since some changes aren't always visible to the latter. #4774 */ + char *buf = NULL; + char *tmp; + DWORD bufsize; + DWORD rc = 1; + const DWORD max = 32768; /* max env var size from MSCRT source */ + + for(;;) { + tmp = realloc(buf, rc); + if(!tmp) { + free(buf); + return NULL; + } + + buf = tmp; + bufsize = rc; + + /* It's possible for rc to be 0 if the variable was found but empty. + Since getenv doesn't make that distinction we ignore it as well. */ + rc = GetEnvironmentVariableA(variable, buf, bufsize); + if(!rc || rc == bufsize || rc > max) { + free(buf); + return NULL; + } + + /* if rc < bufsize then rc is bytes written not including null */ + if(rc < bufsize) + return buf; + + /* else rc is bytes needed, try again */ + } +#else + char *env = getenv(variable); + return (env && env[0])?strdup(env):NULL; +#endif +} + +char *curl_getenv(const char *v) +{ + return GetEnv(v); +} diff --git a/Utilities/cmcurl/lib/getinfo.c b/Utilities/cmcurl/lib/getinfo.c new file mode 100644 index 0000000..f1574e0 --- /dev/null +++ b/Utilities/cmcurl/lib/getinfo.c @@ -0,0 +1,625 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "urldata.h" +#include "getinfo.h" + +#include "vtls/vtls.h" +#include "connect.h" /* Curl_getconnectinfo() */ +#include "progress.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Initialize statistical and informational data. + * + * This function is called in curl_easy_reset, curl_easy_duphandle and at the + * beginning of a perform session. It must reset the session-info variables, + * in particular all variables in struct PureInfo. + */ +CURLcode 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_starttransfer = 0; + pro->timespent = 0; + pro->t_redirect = 0; + pro->is_t_startransfer_set = false; + + info->httpcode = 0; + info->httpproxycode = 0; + info->httpversion = 0; + info->filetime = -1; /* -1 is an illegal time and thus means unknown */ + info->timecond = FALSE; + + info->header_size = 0; + info->request_size = 0; + info->proxyauthavail = 0; + info->httpauthavail = 0; + info->numconnects = 0; + + free(info->contenttype); + info->contenttype = NULL; + + free(info->wouldredirect); + info->wouldredirect = NULL; + + info->conn_primary_ip[0] = '\0'; + info->conn_local_ip[0] = '\0'; + info->conn_primary_port = 0; + info->conn_local_port = 0; + info->retry_after = 0; + + info->conn_scheme = 0; + info->conn_protocol = 0; + +#ifdef USE_SSL + Curl_ssl_free_certinfo(data); +#endif + return CURLE_OK; +} + +static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, + const char **param_charp) +{ + switch(info) { + case CURLINFO_EFFECTIVE_URL: + *param_charp = data->state.url?data->state.url:(char *)""; + break; + case CURLINFO_EFFECTIVE_METHOD: { + const char *m = data->set.str[STRING_CUSTOMREQUEST]; + if(!m) { + if(data->set.opt_no_body) + m = "HEAD"; +#ifndef CURL_DISABLE_HTTP + else { + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + m = "POST"; + break; + case HTTPREQ_PUT: + m = "PUT"; + break; + default: /* this should never happen */ + case HTTPREQ_GET: + m = "GET"; + break; + case HTTPREQ_HEAD: + m = "HEAD"; + break; + } + } +#endif + } + *param_charp = m; + } + break; + case CURLINFO_CONTENT_TYPE: + *param_charp = data->info.contenttype; + break; + case CURLINFO_PRIVATE: + *param_charp = (char *) data->set.private_data; + break; + case CURLINFO_FTP_ENTRY_PATH: + /* Return the entrypath string from the most recent connection. + This pointer was copied from the connectdata structure by FTP. + The actual string may be free()ed by subsequent libcurl calls so + it must be copied to a safer area before the next libcurl call. + Callers must never free it themselves. */ + *param_charp = data->state.most_recent_ftp_entrypath; + break; + case CURLINFO_REDIRECT_URL: + /* Return the URL this request would have been redirected to if that + option had been enabled! */ + *param_charp = data->info.wouldredirect; + break; + case CURLINFO_REFERER: + /* Return the referrer header for this request, or NULL if unset */ + *param_charp = data->state.referer; + break; + case CURLINFO_PRIMARY_IP: + /* Return the ip address of the most recent (primary) connection */ + *param_charp = data->info.conn_primary_ip; + break; + case CURLINFO_LOCAL_IP: + /* Return the source/local ip address of the most recent (primary) + connection */ + *param_charp = data->info.conn_local_ip; + break; + case CURLINFO_RTSP_SESSION_ID: + *param_charp = data->set.str[STRING_RTSP_SESSION_ID]; + break; + case CURLINFO_SCHEME: + *param_charp = data->info.conn_scheme; + break; + case CURLINFO_CAPATH: +#ifdef CURL_CA_PATH + *param_charp = CURL_CA_PATH; +#else + *param_charp = NULL; +#endif + break; + case CURLINFO_CAINFO: +#ifdef CURL_CA_BUNDLE + *param_charp = CURL_CA_BUNDLE; +#else + *param_charp = NULL; +#endif + break; + + default: + return CURLE_UNKNOWN_OPTION; + } + + return CURLE_OK; +} + +static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, + long *param_longp) +{ + curl_socket_t sockfd; + + union { + unsigned long *to_ulong; + long *to_long; + } lptr; + +#ifdef DEBUGBUILD + char *timestr = getenv("CURL_TIME"); + if(timestr) { + unsigned long val = strtol(timestr, NULL, 10); + switch(info) { + case CURLINFO_LOCAL_PORT: + *param_longp = (long)val; + return CURLE_OK; + default: + break; + } + } + /* use another variable for this to allow different values */ + timestr = getenv("CURL_DEBUG_SIZE"); + if(timestr) { + unsigned long val = strtol(timestr, NULL, 10); + switch(info) { + case CURLINFO_HEADER_SIZE: + case CURLINFO_REQUEST_SIZE: + *param_longp = (long)val; + return CURLE_OK; + default: + break; + } + } +#endif + + switch(info) { + case CURLINFO_RESPONSE_CODE: + *param_longp = data->info.httpcode; + break; + case CURLINFO_HTTP_CONNECTCODE: + *param_longp = data->info.httpproxycode; + break; + case CURLINFO_FILETIME: + if(data->info.filetime > LONG_MAX) + *param_longp = LONG_MAX; + else if(data->info.filetime < LONG_MIN) + *param_longp = LONG_MIN; + else + *param_longp = (long)data->info.filetime; + break; + case CURLINFO_HEADER_SIZE: + *param_longp = (long)data->info.header_size; + break; + case CURLINFO_REQUEST_SIZE: + *param_longp = (long)data->info.request_size; + break; + case CURLINFO_SSL_VERIFYRESULT: + *param_longp = data->set.ssl.certverifyresult; + break; +#ifndef CURL_DISABLE_PROXY + case CURLINFO_PROXY_SSL_VERIFYRESULT: + *param_longp = data->set.proxy_ssl.certverifyresult; + break; +#endif + case CURLINFO_REDIRECT_COUNT: + *param_longp = data->state.followlocation; + break; + case CURLINFO_HTTPAUTH_AVAIL: + lptr.to_long = param_longp; + *lptr.to_ulong = data->info.httpauthavail; + break; + case CURLINFO_PROXYAUTH_AVAIL: + lptr.to_long = param_longp; + *lptr.to_ulong = data->info.proxyauthavail; + break; + case CURLINFO_OS_ERRNO: + *param_longp = data->state.os_errno; + break; + case CURLINFO_NUM_CONNECTS: + *param_longp = data->info.numconnects; + break; + case CURLINFO_LASTSOCKET: + sockfd = Curl_getconnectinfo(data, NULL); + + /* note: this is not a good conversion for systems with 64 bit sockets and + 32 bit longs */ + if(sockfd != CURL_SOCKET_BAD) + *param_longp = (long)sockfd; + else + /* this interface is documented to return -1 in case of badness, which + may not be the same as the CURL_SOCKET_BAD value */ + *param_longp = -1; + break; + case CURLINFO_PRIMARY_PORT: + /* Return the (remote) port of the most recent (primary) connection */ + *param_longp = data->info.conn_primary_port; + break; + case CURLINFO_LOCAL_PORT: + /* Return the local port of the most recent (primary) connection */ + *param_longp = data->info.conn_local_port; + break; + case CURLINFO_PROXY_ERROR: + *param_longp = (long)data->info.pxcode; + break; + case CURLINFO_CONDITION_UNMET: + if(data->info.httpcode == 304) + *param_longp = 1L; + else + /* return if the condition prevented the document to get transferred */ + *param_longp = data->info.timecond ? 1L : 0L; + break; +#ifndef CURL_DISABLE_RTSP + case CURLINFO_RTSP_CLIENT_CSEQ: + *param_longp = data->state.rtsp_next_client_CSeq; + break; + case CURLINFO_RTSP_SERVER_CSEQ: + *param_longp = data->state.rtsp_next_server_CSeq; + break; + case CURLINFO_RTSP_CSEQ_RECV: + *param_longp = data->state.rtsp_CSeq_recv; + break; +#endif + case CURLINFO_HTTP_VERSION: + switch(data->info.httpversion) { + case 10: + *param_longp = CURL_HTTP_VERSION_1_0; + break; + case 11: + *param_longp = CURL_HTTP_VERSION_1_1; + break; + case 20: + *param_longp = CURL_HTTP_VERSION_2_0; + break; + case 30: + *param_longp = CURL_HTTP_VERSION_3; + break; + default: + *param_longp = CURL_HTTP_VERSION_NONE; + break; + } + break; + case CURLINFO_PROTOCOL: + *param_longp = data->info.conn_protocol; + break; + default: + return CURLE_UNKNOWN_OPTION; + } + + return CURLE_OK; +} + +#define DOUBLE_SECS(x) (double)(x)/1000000 + +static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, + curl_off_t *param_offt) +{ +#ifdef DEBUGBUILD + char *timestr = getenv("CURL_TIME"); + if(timestr) { + unsigned long val = strtol(timestr, NULL, 10); + switch(info) { + case CURLINFO_TOTAL_TIME_T: + case CURLINFO_NAMELOOKUP_TIME_T: + case CURLINFO_CONNECT_TIME_T: + case CURLINFO_APPCONNECT_TIME_T: + case CURLINFO_PRETRANSFER_TIME_T: + case CURLINFO_STARTTRANSFER_TIME_T: + case CURLINFO_REDIRECT_TIME_T: + case CURLINFO_SPEED_DOWNLOAD_T: + case CURLINFO_SPEED_UPLOAD_T: + *param_offt = (curl_off_t)val; + return CURLE_OK; + default: + break; + } + } +#endif + switch(info) { + case CURLINFO_FILETIME_T: + *param_offt = (curl_off_t)data->info.filetime; + break; + case CURLINFO_SIZE_UPLOAD_T: + *param_offt = data->progress.uploaded; + break; + case CURLINFO_SIZE_DOWNLOAD_T: + *param_offt = data->progress.downloaded; + break; + case CURLINFO_SPEED_DOWNLOAD_T: + *param_offt = data->progress.dlspeed; + break; + case CURLINFO_SPEED_UPLOAD_T: + *param_offt = data->progress.ulspeed; + break; + case CURLINFO_CONTENT_LENGTH_DOWNLOAD_T: + *param_offt = (data->progress.flags & PGRS_DL_SIZE_KNOWN)? + data->progress.size_dl:-1; + break; + case CURLINFO_CONTENT_LENGTH_UPLOAD_T: + *param_offt = (data->progress.flags & PGRS_UL_SIZE_KNOWN)? + data->progress.size_ul:-1; + break; + case CURLINFO_TOTAL_TIME_T: + *param_offt = data->progress.timespent; + break; + case CURLINFO_NAMELOOKUP_TIME_T: + *param_offt = data->progress.t_nslookup; + break; + case CURLINFO_CONNECT_TIME_T: + *param_offt = data->progress.t_connect; + break; + case CURLINFO_APPCONNECT_TIME_T: + *param_offt = data->progress.t_appconnect; + break; + case CURLINFO_PRETRANSFER_TIME_T: + *param_offt = data->progress.t_pretransfer; + break; + case CURLINFO_STARTTRANSFER_TIME_T: + *param_offt = data->progress.t_starttransfer; + break; + case CURLINFO_REDIRECT_TIME_T: + *param_offt = data->progress.t_redirect; + break; + case CURLINFO_RETRY_AFTER: + *param_offt = data->info.retry_after; + break; + case CURLINFO_XFER_ID: + *param_offt = data->id; + break; + case CURLINFO_CONN_ID: + *param_offt = data->conn? + data->conn->connection_id : data->state.recent_conn_id; + break; + default: + return CURLE_UNKNOWN_OPTION; + } + + return CURLE_OK; +} + +static CURLcode getinfo_double(struct Curl_easy *data, CURLINFO info, + double *param_doublep) +{ +#ifdef DEBUGBUILD + char *timestr = getenv("CURL_TIME"); + if(timestr) { + unsigned long val = strtol(timestr, NULL, 10); + switch(info) { + case CURLINFO_TOTAL_TIME: + case CURLINFO_NAMELOOKUP_TIME: + case CURLINFO_CONNECT_TIME: + case CURLINFO_APPCONNECT_TIME: + case CURLINFO_PRETRANSFER_TIME: + case CURLINFO_STARTTRANSFER_TIME: + case CURLINFO_REDIRECT_TIME: + case CURLINFO_SPEED_DOWNLOAD: + case CURLINFO_SPEED_UPLOAD: + *param_doublep = (double)val; + return CURLE_OK; + default: + break; + } + } +#endif + switch(info) { + case CURLINFO_TOTAL_TIME: + *param_doublep = DOUBLE_SECS(data->progress.timespent); + break; + case CURLINFO_NAMELOOKUP_TIME: + *param_doublep = DOUBLE_SECS(data->progress.t_nslookup); + break; + case CURLINFO_CONNECT_TIME: + *param_doublep = DOUBLE_SECS(data->progress.t_connect); + break; + case CURLINFO_APPCONNECT_TIME: + *param_doublep = DOUBLE_SECS(data->progress.t_appconnect); + break; + case CURLINFO_PRETRANSFER_TIME: + *param_doublep = DOUBLE_SECS(data->progress.t_pretransfer); + break; + case CURLINFO_STARTTRANSFER_TIME: + *param_doublep = DOUBLE_SECS(data->progress.t_starttransfer); + break; + case CURLINFO_SIZE_UPLOAD: + *param_doublep = (double)data->progress.uploaded; + break; + case CURLINFO_SIZE_DOWNLOAD: + *param_doublep = (double)data->progress.downloaded; + break; + case CURLINFO_SPEED_DOWNLOAD: + *param_doublep = (double)data->progress.dlspeed; + break; + case CURLINFO_SPEED_UPLOAD: + *param_doublep = (double)data->progress.ulspeed; + break; + case CURLINFO_CONTENT_LENGTH_DOWNLOAD: + *param_doublep = (data->progress.flags & PGRS_DL_SIZE_KNOWN)? + (double)data->progress.size_dl:-1; + break; + case CURLINFO_CONTENT_LENGTH_UPLOAD: + *param_doublep = (data->progress.flags & PGRS_UL_SIZE_KNOWN)? + (double)data->progress.size_ul:-1; + break; + case CURLINFO_REDIRECT_TIME: + *param_doublep = DOUBLE_SECS(data->progress.t_redirect); + break; + + default: + return CURLE_UNKNOWN_OPTION; + } + + return CURLE_OK; +} + +static CURLcode getinfo_slist(struct Curl_easy *data, CURLINFO info, + struct curl_slist **param_slistp) +{ + union { + struct curl_certinfo *to_certinfo; + struct curl_slist *to_slist; + } ptr; + + switch(info) { + case CURLINFO_SSL_ENGINES: + *param_slistp = Curl_ssl_engines_list(data); + break; + case CURLINFO_COOKIELIST: + *param_slistp = Curl_cookie_list(data); + break; + case CURLINFO_CERTINFO: + /* Return the a pointer to the certinfo struct. Not really an slist + pointer but we can pretend it is here */ + ptr.to_certinfo = &data->info.certs; + *param_slistp = ptr.to_slist; + break; + case CURLINFO_TLS_SESSION: + case CURLINFO_TLS_SSL_PTR: + { + struct curl_tlssessioninfo **tsip = (struct curl_tlssessioninfo **) + param_slistp; + struct curl_tlssessioninfo *tsi = &data->tsi; +#ifdef USE_SSL + struct connectdata *conn = data->conn; +#endif + + *tsip = tsi; + tsi->backend = Curl_ssl_backend(); + tsi->internals = NULL; + +#ifdef USE_SSL + if(conn && tsi->backend != CURLSSLBACKEND_NONE) { + tsi->internals = Curl_ssl_get_internals(data, FIRSTSOCKET, info, 0); + } +#endif + } + break; + default: + return CURLE_UNKNOWN_OPTION; + } + + return CURLE_OK; +} + +static CURLcode getinfo_socket(struct Curl_easy *data, CURLINFO info, + curl_socket_t *param_socketp) +{ + switch(info) { + case CURLINFO_ACTIVESOCKET: + *param_socketp = Curl_getconnectinfo(data, NULL); + break; + default: + return CURLE_UNKNOWN_OPTION; + } + + return CURLE_OK; +} + +CURLcode Curl_getinfo(struct Curl_easy *data, CURLINFO info, ...) +{ + va_list arg; + long *param_longp = NULL; + double *param_doublep = NULL; + curl_off_t *param_offt = NULL; + const char **param_charp = NULL; + struct curl_slist **param_slistp = NULL; + curl_socket_t *param_socketp = NULL; + int type; + CURLcode result = CURLE_UNKNOWN_OPTION; + + if(!data) + return CURLE_BAD_FUNCTION_ARGUMENT; + + va_start(arg, info); + + type = CURLINFO_TYPEMASK & (int)info; + switch(type) { + case CURLINFO_STRING: + param_charp = va_arg(arg, const char **); + if(param_charp) + result = getinfo_char(data, info, param_charp); + break; + case CURLINFO_LONG: + param_longp = va_arg(arg, long *); + if(param_longp) + result = getinfo_long(data, info, param_longp); + break; + case CURLINFO_DOUBLE: + param_doublep = va_arg(arg, double *); + if(param_doublep) + result = getinfo_double(data, info, param_doublep); + break; + case CURLINFO_OFF_T: + param_offt = va_arg(arg, curl_off_t *); + if(param_offt) + result = getinfo_offt(data, info, param_offt); + break; + case CURLINFO_SLIST: + param_slistp = va_arg(arg, struct curl_slist **); + if(param_slistp) + result = getinfo_slist(data, info, param_slistp); + break; + case CURLINFO_SOCKET: + param_socketp = va_arg(arg, curl_socket_t *); + if(param_socketp) + result = getinfo_socket(data, info, param_socketp); + break; + default: + break; + } + + va_end(arg); + + return result; +} diff --git a/Utilities/cmcurl/lib/getinfo.h b/Utilities/cmcurl/lib/getinfo.h new file mode 100644 index 0000000..56bb440 --- /dev/null +++ b/Utilities/cmcurl/lib/getinfo.h @@ -0,0 +1,29 @@ +#ifndef HEADER_CURL_GETINFO_H +#define HEADER_CURL_GETINFO_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +CURLcode Curl_getinfo(struct Curl_easy *data, CURLINFO info, ...); +CURLcode Curl_initinfo(struct Curl_easy *data); + +#endif /* HEADER_CURL_GETINFO_H */ diff --git a/Utilities/cmcurl/lib/gopher.c b/Utilities/cmcurl/lib/gopher.c new file mode 100644 index 0000000..61e41b7 --- /dev/null +++ b/Utilities/cmcurl/lib/gopher.c @@ -0,0 +1,242 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_GOPHER + +#include "urldata.h" +#include <curl/curl.h> +#include "transfer.h" +#include "sendf.h" +#include "cfilters.h" +#include "connect.h" +#include "progress.h" +#include "gopher.h" +#include "select.h" +#include "strdup.h" +#include "vtls/vtls.h" +#include "url.h" +#include "escape.h" +#include "warnless.h" +#include "curl_printf.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/* + * Forward declarations. + */ + +static CURLcode gopher_do(struct Curl_easy *data, bool *done); +#ifdef USE_SSL +static CURLcode gopher_connect(struct Curl_easy *data, bool *done); +static CURLcode gopher_connecting(struct Curl_easy *data, bool *done); +#endif + +/* + * Gopher protocol handler. + * This is also a nice simple template to build off for simple + * connect-command-download protocols. + */ + +const struct Curl_handler Curl_handler_gopher = { + "GOPHER", /* scheme */ + ZERO_NULL, /* setup_connection */ + gopher_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_GOPHER, /* defport */ + CURLPROTO_GOPHER, /* protocol */ + CURLPROTO_GOPHER, /* family */ + PROTOPT_NONE /* flags */ +}; + +#ifdef USE_SSL +const struct Curl_handler Curl_handler_gophers = { + "GOPHERS", /* scheme */ + ZERO_NULL, /* setup_connection */ + gopher_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + gopher_connect, /* connect_it */ + gopher_connecting, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_GOPHER, /* defport */ + CURLPROTO_GOPHERS, /* protocol */ + CURLPROTO_GOPHER, /* family */ + PROTOPT_SSL /* flags */ +}; + +static CURLcode gopher_connect(struct Curl_easy *data, bool *done) +{ + (void)data; + (void)done; + return CURLE_OK; +} + +static CURLcode gopher_connecting(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + CURLcode result; + + result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done); + if(result) + connclose(conn, "Failed TLS connection"); + *done = TRUE; + return result; +} +#endif + +static CURLcode gopher_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + char *gopherpath; + char *path = data->state.up.path; + char *query = data->state.up.query; + char *sel = NULL; + char *sel_org = NULL; + timediff_t timeout_ms; + ssize_t amount, k; + size_t len; + int what; + + *done = TRUE; /* unconditionally */ + + /* path is guaranteed non-NULL */ + DEBUGASSERT(path); + + if(query) + gopherpath = aprintf("%s?%s", path, query); + else + gopherpath = strdup(path); + + if(!gopherpath) + return CURLE_OUT_OF_MEMORY; + + /* Create selector. Degenerate cases: / and /1 => convert to "" */ + if(strlen(gopherpath) <= 2) { + sel = (char *)""; + len = strlen(sel); + free(gopherpath); + } + else { + char *newp; + + /* Otherwise, drop / and the first character (i.e., item type) ... */ + newp = gopherpath; + newp += 2; + + /* ... and finally unescape */ + result = Curl_urldecode(newp, 0, &sel, &len, REJECT_ZERO); + free(gopherpath); + if(result) + return result; + sel_org = sel; + } + + k = curlx_uztosz(len); + + for(;;) { + /* Break out of the loop if the selector is empty because OpenSSL and/or + LibreSSL fail with errno 0 if this is the case. */ + if(strlen(sel) < 1) + break; + + result = Curl_nwrite(data, FIRSTSOCKET, sel, k, &amount); + if(!result) { /* Which may not have written it all! */ + result = Curl_client_write(data, CLIENTWRITE_HEADER, sel, amount); + if(result) + break; + + k -= amount; + sel += amount; + if(k < 1) + break; /* but it did write it all */ + } + else + break; + + timeout_ms = Curl_timeleft(data, NULL, FALSE); + if(timeout_ms < 0) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + if(!timeout_ms) + timeout_ms = TIMEDIFF_T_MAX; + + /* Don't busyloop. The entire loop thing is a work-around 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 aren't + 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; + } + } + + free(sel_org); + + if(!result) + result = Curl_nwrite(data, FIRSTSOCKET, "\r\n", 2, &amount); + if(result) { + failf(data, "Failed sending Gopher request"); + return result; + } + result = Curl_client_write(data, CLIENTWRITE_HEADER, (char *)"\r\n", 2); + if(result) + return result; + + Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + return CURLE_OK; +} +#endif /* CURL_DISABLE_GOPHER */ diff --git a/Utilities/cmcurl/lib/gopher.h b/Utilities/cmcurl/lib/gopher.h new file mode 100644 index 0000000..9e3365b --- /dev/null +++ b/Utilities/cmcurl/lib/gopher.h @@ -0,0 +1,34 @@ +#ifndef HEADER_CURL_GOPHER_H +#define HEADER_CURL_GOPHER_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#ifndef CURL_DISABLE_GOPHER +extern const struct Curl_handler Curl_handler_gopher; +#ifdef USE_SSL +extern const struct Curl_handler Curl_handler_gophers; +#endif +#endif + +#endif /* HEADER_CURL_GOPHER_H */ diff --git a/Utilities/cmcurl/lib/hash.c b/Utilities/cmcurl/lib/hash.c new file mode 100644 index 0000000..30f28e2 --- /dev/null +++ b/Utilities/cmcurl/lib/hash.c @@ -0,0 +1,370 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "hash.h" +#include "llist.h" +#include "curl_memory.h" + +/* The last #include file should be: */ +#include "memdebug.h" + +static void +hash_element_dtor(void *user, void *element) +{ + struct Curl_hash *h = (struct Curl_hash *) user; + struct Curl_hash_element *e = (struct Curl_hash_element *) element; + + if(e->ptr) { + h->dtor(e->ptr); + e->ptr = NULL; + } + + e->key_len = 0; + + free(e); +} + +/* Initializes a hash structure. + * Return 1 on error, 0 is fine. + * + * @unittest: 1602 + * @unittest: 1603 + */ +void +Curl_hash_init(struct Curl_hash *h, + int slots, + hash_function hfunc, + comp_function comparator, + Curl_hash_dtor dtor) +{ + DEBUGASSERT(h); + DEBUGASSERT(slots); + DEBUGASSERT(hfunc); + DEBUGASSERT(comparator); + DEBUGASSERT(dtor); + + h->table = NULL; + h->hash_func = hfunc; + h->comp_func = comparator; + h->dtor = dtor; + h->size = 0; + h->slots = slots; +} + +static struct Curl_hash_element * +mk_hash_element(const void *key, size_t key_len, const void *p) +{ + /* allocate the struct plus memory after it to store the key */ + struct Curl_hash_element *he = malloc(sizeof(struct Curl_hash_element) + + key_len); + if(he) { + /* copy the key */ + memcpy(he->key, key, key_len); + he->key_len = key_len; + he->ptr = (void *) p; + } + return he; +} + +#define FETCH_LIST(x,y,z) &x->table[x->hash_func(y, z, x->slots)] + +/* Insert the data in the hash. If there already was a match in the hash, that + * data is replaced. This function also "lazily" allocates the table if + * needed, as it isn't done in the _init function (anymore). + * + * @unittest: 1305 + * @unittest: 1602 + * @unittest: 1603 + */ +void * +Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) +{ + struct Curl_hash_element *he; + struct Curl_llist_element *le; + struct Curl_llist *l; + + DEBUGASSERT(h); + DEBUGASSERT(h->slots); + if(!h->table) { + int i; + h->table = malloc(h->slots * sizeof(struct Curl_llist)); + if(!h->table) + return NULL; /* OOM */ + for(i = 0; i < h->slots; ++i) + Curl_llist_init(&h->table[i], hash_element_dtor); + } + + l = FETCH_LIST(h, key, key_len); + + for(le = l->head; le; le = le->next) { + he = (struct Curl_hash_element *) le->ptr; + if(h->comp_func(he->key, he->key_len, key, key_len)) { + Curl_llist_remove(l, le, (void *)h); + --h->size; + break; + } + } + + he = mk_hash_element(key, key_len, p); + if(he) { + Curl_llist_insert_next(l, l->tail, he, &he->list); + ++h->size; + return p; /* return the new entry */ + } + + return NULL; /* failure */ +} + +/* Remove the identified hash entry. + * Returns non-zero on failure. + * + * @unittest: 1603 + */ +int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) +{ + struct Curl_llist_element *le; + struct Curl_llist *l; + + DEBUGASSERT(h); + DEBUGASSERT(h->slots); + if(h->table) { + l = FETCH_LIST(h, key, key_len); + + for(le = l->head; le; le = le->next) { + struct Curl_hash_element *he = le->ptr; + if(h->comp_func(he->key, he->key_len, key, key_len)) { + Curl_llist_remove(l, le, (void *) h); + --h->size; + return 0; + } + } + } + return 1; +} + +/* Retrieves a hash element. + * + * @unittest: 1603 + */ +void * +Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len) +{ + struct Curl_llist_element *le; + struct Curl_llist *l; + + DEBUGASSERT(h); + if(h->table) { + DEBUGASSERT(h->slots); + l = FETCH_LIST(h, key, key_len); + for(le = l->head; le; le = le->next) { + struct Curl_hash_element *he = le->ptr; + if(h->comp_func(he->key, he->key_len, key, key_len)) { + return he->ptr; + } + } + } + + return NULL; +} + +#if defined(DEBUGBUILD) && defined(AGGRESSIVE_TEST) +void +Curl_hash_apply(Curl_hash *h, void *user, + void (*cb)(void *user, void *ptr)) +{ + struct Curl_llist_element *le; + int i; + + for(i = 0; i < h->slots; ++i) { + for(le = (h->table[i])->head; + le; + le = le->next) { + Curl_hash_element *el = le->ptr; + cb(user, el->ptr); + } + } +} +#endif + +/* Destroys all the entries in the given hash and resets its attributes, + * prepping the given hash for [static|dynamic] deallocation. + * + * @unittest: 1305 + * @unittest: 1602 + * @unittest: 1603 + */ +void +Curl_hash_destroy(struct Curl_hash *h) +{ + if(h->table) { + int i; + for(i = 0; i < h->slots; ++i) { + Curl_llist_destroy(&h->table[i], (void *) h); + } + Curl_safefree(h->table); + } + h->size = 0; + h->slots = 0; +} + +/* Removes all the entries in the given hash. + * + * @unittest: 1602 + */ +void +Curl_hash_clean(struct Curl_hash *h) +{ + Curl_hash_clean_with_criterium(h, NULL, NULL); +} + +/* Cleans all entries that pass the comp function criteria. */ +void +Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, + int (*comp)(void *, void *)) +{ + struct Curl_llist_element *le; + struct Curl_llist_element *lnext; + struct Curl_llist *list; + int i; + + if(!h || !h->table) + return; + + for(i = 0; i < h->slots; ++i) { + list = &h->table[i]; + le = list->head; /* get first list entry */ + while(le) { + struct Curl_hash_element *he = le->ptr; + lnext = le->next; + /* ask the callback function if we shall remove this entry or not */ + if(!comp || comp(user, he->ptr)) { + Curl_llist_remove(list, le, (void *) h); + --h->size; /* one less entry in the hash now */ + } + le = lnext; + } + } +} + +size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num) +{ + const char *key_str = (const char *) key; + const char *end = key_str + key_length; + size_t h = 5381; + + while(key_str < end) { + h += h << 5; + h ^= *key_str++; + } + + return (h % slots_num); +} + +size_t Curl_str_key_compare(void *k1, size_t key1_len, + void *k2, size_t key2_len) +{ + if((key1_len == key2_len) && !memcmp(k1, k2, key1_len)) + return 1; + + return 0; +} + +void Curl_hash_start_iterate(struct Curl_hash *hash, + struct Curl_hash_iterator *iter) +{ + iter->hash = hash; + iter->slot_index = 0; + iter->current_element = NULL; +} + +struct Curl_hash_element * +Curl_hash_next_element(struct Curl_hash_iterator *iter) +{ + struct Curl_hash *h = iter->hash; + + if(!h->table) + return NULL; /* empty hash, nothing to return */ + + /* Get the next element in the current list, if any */ + if(iter->current_element) + iter->current_element = iter->current_element->next; + + /* If we have reached the end of the list, find the next one */ + if(!iter->current_element) { + int i; + for(i = iter->slot_index; i < h->slots; i++) { + if(h->table[i].head) { + iter->current_element = h->table[i].head; + iter->slot_index = i + 1; + break; + } + } + } + + if(iter->current_element) { + struct Curl_hash_element *he = iter->current_element->ptr; + return he; + } + return NULL; +} + +#if 0 /* useful function for debugging hashes and their contents */ +void Curl_hash_print(struct Curl_hash *h, + void (*func)(void *)) +{ + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + int last_index = -1; + + if(!h) + return; + + fprintf(stderr, "=Hash dump=\n"); + + Curl_hash_start_iterate(h, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + if(iter.slot_index != last_index) { + fprintf(stderr, "index %d:", iter.slot_index); + if(last_index >= 0) { + fprintf(stderr, "\n"); + } + last_index = iter.slot_index; + } + + if(func) + func(he->ptr); + else + fprintf(stderr, " [%p]", (void *)he->ptr); + + he = Curl_hash_next_element(&iter); + } + fprintf(stderr, "\n"); +} +#endif diff --git a/Utilities/cmcurl/lib/hash.h b/Utilities/cmcurl/lib/hash.h new file mode 100644 index 0000000..9cfffc2 --- /dev/null +++ b/Utilities/cmcurl/lib/hash.h @@ -0,0 +1,102 @@ +#ifndef HEADER_CURL_HASH_H +#define HEADER_CURL_HASH_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <stddef.h> + +#include "llist.h" + +/* Hash function prototype */ +typedef size_t (*hash_function) (void *key, + size_t key_length, + size_t slots_num); + +/* + Comparator function prototype. Compares two keys. +*/ +typedef size_t (*comp_function) (void *key1, + size_t key1_len, + void *key2, + size_t key2_len); + +typedef void (*Curl_hash_dtor)(void *); + +struct Curl_hash { + struct Curl_llist *table; + + /* Hash function to be used for this hash table */ + hash_function hash_func; + + /* Comparator function to compare keys */ + comp_function comp_func; + Curl_hash_dtor dtor; + int slots; + size_t size; +}; + +struct Curl_hash_element { + struct Curl_llist_element list; + void *ptr; + size_t key_len; + char key[1]; /* allocated memory following the struct */ +}; + +struct Curl_hash_iterator { + struct Curl_hash *hash; + int slot_index; + struct Curl_llist_element *current_element; +}; + +void Curl_hash_init(struct Curl_hash *h, + int slots, + hash_function hfunc, + comp_function comparator, + Curl_hash_dtor dtor); + +void *Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p); +int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len); +void *Curl_hash_pick(struct Curl_hash *, void *key, size_t key_len); +void Curl_hash_apply(struct Curl_hash *h, void *user, + void (*cb)(void *user, void *ptr)); +#define Curl_hash_count(h) ((h)->size) +void Curl_hash_destroy(struct Curl_hash *h); +void Curl_hash_clean(struct Curl_hash *h); +void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, + int (*comp)(void *, void *)); +size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num); +size_t Curl_str_key_compare(void *k1, size_t key1_len, void *k2, + size_t key2_len); +void Curl_hash_start_iterate(struct Curl_hash *hash, + struct Curl_hash_iterator *iter); +struct Curl_hash_element * +Curl_hash_next_element(struct Curl_hash_iterator *iter); + +void Curl_hash_print(struct Curl_hash *h, + void (*func)(void *)); + + +#endif /* HEADER_CURL_HASH_H */ diff --git a/Utilities/cmcurl/lib/headers.c b/Utilities/cmcurl/lib/headers.c new file mode 100644 index 0000000..3ff4d5e --- /dev/null +++ b/Utilities/cmcurl/lib/headers.c @@ -0,0 +1,395 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include "strdup.h" +#include "strcase.h" +#include "headers.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HEADERS_API) + +/* Generate the curl_header struct for the user. This function MUST assign all + struct fields in the output struct. */ +static void copy_header_external(struct Curl_header_store *hs, + size_t index, + size_t amount, + struct Curl_llist_element *e, + struct curl_header *hout) +{ + struct curl_header *h = hout; + h->name = hs->name; + h->value = hs->value; + h->amount = amount; + h->index = index; + /* this will randomly OR a reserved bit for the sole purpose of making it + impossible for applications to do == comparisons, as that would otherwise + be very tempting and then lead to the reserved bits not being reserved + anymore. */ + h->origin = hs->type | (1<<27); + h->anchor = e; +} + +/* public API */ +CURLHcode curl_easy_header(CURL *easy, + const char *name, + size_t nameindex, + unsigned int type, + int request, + struct curl_header **hout) +{ + struct Curl_llist_element *e; + struct Curl_llist_element *e_pick = NULL; + struct Curl_easy *data = easy; + size_t match = 0; + size_t amount = 0; + struct Curl_header_store *hs = NULL; + struct Curl_header_store *pick = NULL; + if(!name || !hout || !data || + (type > (CURLH_HEADER|CURLH_TRAILER|CURLH_CONNECT|CURLH_1XX| + CURLH_PSEUDO)) || !type || (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; + + /* we need a first round to count amount of this header */ + for(e = data->state.httphdrs.head; e; e = e->next) { + hs = e->ptr; + if(strcasecompare(hs->name, name) && + (hs->type & type) && + (hs->request == request)) { + amount++; + pick = hs; + e_pick = e; + } + } + if(!amount) + return CURLHE_MISSING; + else if(nameindex >= amount) + return CURLHE_BADINDEX; + + if(nameindex == amount - 1) + /* if the last or only occurrence is what's asked for, then we know it */ + hs = pick; + else { + for(e = data->state.httphdrs.head; e; e = e->next) { + hs = e->ptr; + if(strcasecompare(hs->name, name) && + (hs->type & type) && + (hs->request == request) && + (match++ == nameindex)) { + e_pick = e; + break; + } + } + if(!e) /* this shouldn't happen */ + return CURLHE_MISSING; + } + /* 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; +} + +/* public API */ +struct curl_header *curl_easy_nextheader(CURL *easy, + unsigned int type, + int request, + struct curl_header *prev) +{ + struct Curl_easy *data = easy; + struct Curl_llist_element *pick; + struct Curl_llist_element *e; + struct Curl_header_store *hs; + size_t amount = 0; + size_t index = 0; + + if(request > data->state.requests) + return NULL; + if(request == -1) + request = data->state.requests; + + if(prev) { + pick = prev->anchor; + if(!pick) + /* something is wrong */ + return NULL; + pick = pick->next; + } + else + pick = data->state.httphdrs.head; + + if(pick) { + /* make sure it is the next header of the desired type */ + do { + hs = pick->ptr; + if((hs->type & type) && (hs->request == request)) + break; + pick = pick->next; + } while(pick); + } + + if(!pick) + /* no more headers available */ + return NULL; + + hs = pick->ptr; + + /* count number of occurrences of this name within the mask and figure out + the index for the currently selected entry */ + for(e = data->state.httphdrs.head; e; e = e->next) { + struct Curl_header_store *check = e->ptr; + if(strcasecompare(hs->name, check->name) && + (check->request == request) && + (check->type & type)) + amount++; + if(e == pick) + index = amount - 1; + } + + copy_header_external(hs, index, amount, pick, + &data->state.headerout[1]); + return &data->state.headerout[1]; +} + +static CURLcode namevalue(char *header, size_t hlen, unsigned int type, + char **name, char **value) +{ + char *end = header + hlen - 1; /* point to the last byte */ + DEBUGASSERT(hlen); + *name = header; + + if(type == CURLH_PSEUDO) { + if(*header != ':') + return CURLE_BAD_FUNCTION_ARGUMENT; + header++; + } + + /* Find the end of the header name */ + while(*header && (*header != ':')) + ++header; + + if(*header) + /* Skip over colon, null it */ + *header++ = 0; + else + return CURLE_BAD_FUNCTION_ARGUMENT; + + /* skip all leading space letters */ + while(*header && ISBLANK(*header)) + header++; + + *value = header; + + /* skip all trailing space letters */ + while((end > header) && ISSPACE(*end)) + *end-- = 0; /* nul terminate */ + return CURLE_OK; +} + +static CURLcode unfold_value(struct Curl_easy *data, const char *value, + size_t vlen) /* length of the incoming header */ +{ + struct Curl_header_store *hs; + struct Curl_header_store *newhs; + size_t olen; /* length of the old value */ + size_t oalloc; /* length of the old name + value + separator */ + size_t offset; + DEBUGASSERT(data->state.prevhead); + hs = data->state.prevhead; + olen = strlen(hs->value); + offset = hs->value - hs->buffer; + oalloc = olen + offset + 1; + + /* skip all trailing space letters */ + while(vlen && ISSPACE(value[vlen - 1])) + vlen--; + + /* save only one leading space */ + while((vlen > 1) && ISBLANK(value[0]) && ISBLANK(value[1])) { + vlen--; + value++; + } + + /* since this header block might move in the realloc below, it needs to + first be unlinked from the list and then re-added again after the + realloc */ + Curl_llist_remove(&data->state.httphdrs, &hs->node, NULL); + + /* new size = struct + new value length + old name+value length */ + newhs = Curl_saferealloc(hs, sizeof(*hs) + vlen + oalloc + 1); + if(!newhs) + return CURLE_OUT_OF_MEMORY; + /* ->name' and ->value point into ->buffer (to keep the header allocation + in a single memory block), which now potentially have moved. Adjust + them. */ + newhs->name = newhs->buffer; + newhs->value = &newhs->buffer[offset]; + + /* put the data at the end of the previous data, not the newline */ + memcpy(&newhs->value[olen], value, vlen); + newhs->value[olen + vlen] = 0; /* null-terminate at newline */ + + /* insert this node into the list of headers */ + Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail, + newhs, &newhs->node); + data->state.prevhead = newhs; + return CURLE_OK; +} + + +/* + * Curl_headers_push() gets passed a full HTTP header to store. It gets called + * immediately before the header callback. The header is CRLF terminated. + */ +CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, + unsigned char type) +{ + char *value = NULL; + char *name = NULL; + char *end; + size_t hlen; /* length of the incoming header */ + struct Curl_header_store *hs; + CURLcode result = CURLE_OUT_OF_MEMORY; + + if((header[0] == '\r') || (header[0] == '\n')) + /* ignore the body separator */ + return CURLE_OK; + + end = strchr(header, '\r'); + if(!end) { + end = strchr(header, '\n'); + if(!end) + return CURLE_BAD_FUNCTION_ARGUMENT; + } + hlen = end - header + 1; + + if((header[0] == ' ') || (header[0] == '\t')) { + if(data->state.prevhead) + /* line folding, append value to the previous header's value */ + return unfold_value(data, header, hlen); + else { + /* Can't unfold without a previous header. Instead of erroring, just + pass the leading blanks. */ + while(hlen && ISBLANK(*header)) { + header++; + hlen--; + } + if(!hlen) + return CURLE_WEIRD_SERVER_REPLY; + } + } + + hs = calloc(1, sizeof(*hs) + hlen); + if(!hs) + return CURLE_OUT_OF_MEMORY; + memcpy(hs->buffer, header, hlen); + hs->buffer[hlen] = 0; /* nul terminate */ + + result = namevalue(hs->buffer, hlen, type, &name, &value); + if(result) + goto fail; + + hs->name = name; + hs->value = value; + hs->type = type; + hs->request = data->state.requests; + + /* insert this node into the list of headers */ + Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail, + hs, &hs->node); + data->state.prevhead = hs; + return CURLE_OK; +fail: + free(hs); + return result; +} + +/* + * Curl_headers_init(). Init the headers subsystem. + */ +static void headers_init(struct Curl_easy *data) +{ + Curl_llist_init(&data->state.httphdrs, NULL); + data->state.prevhead = NULL; +} + +/* + * Curl_headers_cleanup(). Free all stored headers and associated memory. + */ +CURLcode Curl_headers_cleanup(struct Curl_easy *data) +{ + struct Curl_llist_element *e; + struct Curl_llist_element *n; + + for(e = data->state.httphdrs.head; e; e = n) { + struct Curl_header_store *hs = e->ptr; + n = e->next; + free(hs); + } + headers_init(data); + return CURLE_OK; +} + +#else /* HTTP-disabled builds below */ + +CURLHcode curl_easy_header(CURL *easy, + const char *name, + size_t index, + unsigned int origin, + int request, + struct curl_header **hout) +{ + (void)easy; + (void)name; + (void)index; + (void)origin; + (void)request; + (void)hout; + return CURLHE_NOT_BUILT_IN; +} + +struct curl_header *curl_easy_nextheader(CURL *easy, + unsigned int type, + int request, + struct curl_header *prev) +{ + (void)easy; + (void)type; + (void)request; + (void)prev; + return NULL; +} +#endif diff --git a/Utilities/cmcurl/lib/headers.h b/Utilities/cmcurl/lib/headers.h new file mode 100644 index 0000000..a5229ea --- /dev/null +++ b/Utilities/cmcurl/lib/headers.h @@ -0,0 +1,55 @@ +#ifndef HEADER_CURL_HEADER_H +#define HEADER_CURL_HEADER_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HEADERS_API) + +struct Curl_header_store { + struct Curl_llist_element node; + char *name; /* points into 'buffer' */ + char *value; /* points into 'buffer */ + int request; /* 0 is the first request, then 1.. 2.. */ + unsigned char type; /* CURLH_* defines */ + char buffer[1]; /* this is the raw header blob */ +}; + +/* + * Curl_headers_push() gets passed a full header to store. + */ +CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, + unsigned char type); + +/* + * Curl_headers_cleanup(). Free all stored headers and associated memory. + */ +CURLcode Curl_headers_cleanup(struct Curl_easy *data); + +#else +#define Curl_headers_push(x,y,z) CURLE_OK +#define Curl_headers_cleanup(x) Curl_nop_stmt +#endif + +#endif /* HEADER_CURL_HEADER_H */ diff --git a/Utilities/cmcurl/lib/hmac.c b/Utilities/cmcurl/lib/hmac.c new file mode 100644 index 0000000..4019b67 --- /dev/null +++ b/Utilities/cmcurl/lib/hmac.c @@ -0,0 +1,173 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC2104 Keyed-Hashing for Message Authentication + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ + || !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) + +#include <curl/curl.h> + +#include "curl_hmac.h" +#include "curl_memory.h" +#include "warnless.h" + +/* The last #include file should be: */ +#include "memdebug.h" + +/* + * Generic HMAC algorithm. + * + * This module computes HMAC digests based on any hash function. Parameters + * and computing procedures are set-up dynamically at HMAC computation context + * initialization. + */ + +static const unsigned char hmac_ipad = 0x36; +static const unsigned char hmac_opad = 0x5C; + + + +struct HMAC_context * +Curl_HMAC_init(const struct HMAC_params *hashparams, + const unsigned char *key, + unsigned int keylen) +{ + size_t i; + struct HMAC_context *ctxt; + unsigned char *hkey; + unsigned char b; + + /* Create HMAC context. */ + i = sizeof(*ctxt) + 2 * hashparams->hmac_ctxtsize + + hashparams->hmac_resultlen; + ctxt = malloc(i); + + if(!ctxt) + return ctxt; + + ctxt->hmac_hash = hashparams; + ctxt->hmac_hashctxt1 = (void *) (ctxt + 1); + ctxt->hmac_hashctxt2 = (void *) ((char *) ctxt->hmac_hashctxt1 + + hashparams->hmac_ctxtsize); + + /* If the key is too long, replace it by its hash digest. */ + if(keylen > hashparams->hmac_maxkeylen) { + (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1); + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, key, keylen); + hkey = (unsigned char *) ctxt->hmac_hashctxt2 + hashparams->hmac_ctxtsize; + (*hashparams->hmac_hfinal)(hkey, ctxt->hmac_hashctxt1); + key = hkey; + keylen = hashparams->hmac_resultlen; + } + + /* Prime the two hash contexts with the modified key. */ + (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1); + (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt2); + + for(i = 0; i < keylen; i++) { + b = (unsigned char)(*key ^ hmac_ipad); + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &b, 1); + b = (unsigned char)(*key++ ^ hmac_opad); + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &b, 1); + } + + for(; i < hashparams->hmac_maxkeylen; i++) { + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &hmac_ipad, 1); + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &hmac_opad, 1); + } + + /* Done, return pointer to HMAC context. */ + return ctxt; +} + +int Curl_HMAC_update(struct HMAC_context *ctxt, + const unsigned char *data, + unsigned int len) +{ + /* Update first hash calculation. */ + (*ctxt->hmac_hash->hmac_hupdate)(ctxt->hmac_hashctxt1, data, len); + return 0; +} + + +int Curl_HMAC_final(struct HMAC_context *ctxt, unsigned char *result) +{ + const struct HMAC_params *hashparams = ctxt->hmac_hash; + + /* Do not get result if called with a null parameter: only release + storage. */ + + if(!result) + result = (unsigned char *) ctxt->hmac_hashctxt2 + + ctxt->hmac_hash->hmac_ctxtsize; + + (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt1); + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, + result, hashparams->hmac_resultlen); + (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt2); + free((char *) ctxt); + return 0; +} + +/* + * Curl_hmacit() + * + * This is used to generate a HMAC hash, for the specified input data, given + * the specified hash function and key. + * + * Parameters: + * + * hashparams [in] - The hash function (Curl_HMAC_MD5). + * key [in] - The key to use. + * keylen [in] - The length of the key. + * data [in] - The data to encrypt. + * datalen [in] - The length of the data. + * output [in/out] - The output buffer. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_hmacit(const struct HMAC_params *hashparams, + const unsigned char *key, const size_t keylen, + const unsigned char *data, const size_t datalen, + unsigned char *output) +{ + struct HMAC_context *ctxt = + Curl_HMAC_init(hashparams, key, curlx_uztoui(keylen)); + + if(!ctxt) + return CURLE_OUT_OF_MEMORY; + + /* Update the digest with the given challenge */ + Curl_HMAC_update(ctxt, data, curlx_uztoui(datalen)); + + /* Finalise the digest */ + Curl_HMAC_final(ctxt, output); + + return CURLE_OK; +} + +#endif /* Using NTLM (without SSPI) or AWS */ diff --git a/Utilities/cmcurl/lib/hostasyn.c b/Utilities/cmcurl/lib/hostasyn.c new file mode 100644 index 0000000..faf01c5 --- /dev/null +++ b/Utilities/cmcurl/lib/hostasyn.c @@ -0,0 +1,124 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +/*********************************************************************** + * Only for builds using asynchronous name resolves + **********************************************************************/ +#ifdef CURLRES_ASYNCH + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "url.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/* + * Curl_addrinfo_callback() gets called by ares, gethostbyname_thread() + * or getaddrinfo_thread() when we got the name resolved (or not!). + * + * If the status argument is CURL_ASYNC_SUCCESS, this function takes + * ownership of the Curl_addrinfo passed, storing the resolved data + * in the DNS cache. + * + * The storage operation locks and unlocks the DNS cache. + */ +CURLcode Curl_addrinfo_callback(struct Curl_easy *data, + int status, + struct Curl_addrinfo *ai) +{ + struct connectdata *conn = data->conn; + struct Curl_dns_entry *dns = NULL; + CURLcode result = CURLE_OK; + + conn->resolve_async.status = status; + + if(CURL_ASYNC_SUCCESS == status) { + if(ai) { + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + dns = Curl_cache_addr(data, ai, + conn->resolve_async.hostname, 0, + conn->resolve_async.port); + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + + if(!dns) { + /* failed to store, cleanup and return error */ + Curl_freeaddrinfo(ai); + result = CURLE_OUT_OF_MEMORY; + } + } + else { + result = CURLE_OUT_OF_MEMORY; + } + } + + conn->resolve_async.dns = dns; + + /* Set async.done TRUE last in this function since it may be used multi- + threaded and once this is TRUE the other thread may read fields from the + async struct */ + conn->resolve_async.done = TRUE; + + /* IPv4: The input hostent struct will be freed by ares when we return from + this function */ + return result; +} + +/* + * Curl_getaddrinfo() is the generic low-level name resolve API within this + * source file. There are several versions of this function - for different + * name resolve layers (selected at build-time). They all take this same set + * of arguments + */ +struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) +{ + return Curl_resolver_getaddrinfo(data, hostname, port, waitp); +} + +#endif /* CURLRES_ASYNCH */ diff --git a/Utilities/cmcurl/lib/hostip.c b/Utilities/cmcurl/lib/hostip.c new file mode 100644 index 0000000..e7c318a --- /dev/null +++ b/Utilities/cmcurl/lib/hostip.c @@ -0,0 +1,1463 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN6_H +#include <netinet/in6.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include <setjmp.h> +#include <signal.h> + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "rand.h" +#include "share.h" +#include "url.h" +#include "inet_ntop.h" +#include "inet_pton.h" +#include "multiif.h" +#include "doh.h" +#include "warnless.h" +#include "strcase.h" +#include "easy_lock.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#if defined(CURLRES_SYNCH) && \ + defined(HAVE_ALARM) && \ + defined(SIGALRM) && \ + defined(HAVE_SIGSETJMP) && \ + defined(GLOBAL_INIT_IS_THREADSAFE) +/* alarm-based timeouts can only be used with all the dependencies satisfied */ +#define USE_ALARM_TIMEOUT +#endif + +#define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */ + +#define MAX_DNS_CACHE_SIZE 29999 + +/* + * hostip.c explained + * ================== + * + * The main COMPILE-TIME DEFINES to keep in mind when reading the host*.c + * source file are these: + * + * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use + * that. The host may not be able to resolve IPv6, but we don't really have to + * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4 + * defined. + * + * CURLRES_ARES - is defined if libcurl is built to use c-ares for + * asynchronous name resolves. This can be Windows or *nix. + * + * CURLRES_THREADED - is defined if libcurl is built to run under (native) + * Windows, and then the name resolve will be done in a new thread, and the + * supported API will be the same as for ares-builds. + * + * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If + * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is + * defined. + * + * The host*.c sources files are split up like this: + * + * hostip.c - method-independent resolver functions and utility functions + * hostasyn.c - functions for asynchronous name resolves + * hostsyn.c - functions for synchronous name resolves + * hostip4.c - IPv4 specific functions + * hostip6.c - IPv6 specific functions + * + * The two asynchronous name resolver backends are implemented in: + * asyn-ares.c - functions for ares-using name resolves + * asyn-thread.c - functions for threaded name resolves + + * The hostip.h is the united header file for all this. It defines the + * CURLRES_* defines based on the config*.h and curl_setup.h defines. + */ + +static void freednsentry(void *freethis); + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void show_resolve_info(struct Curl_easy *data, + struct Curl_dns_entry *dns); +#else +#define show_resolve_info(x,y) Curl_nop_stmt +#endif + +/* + * Curl_printable_address() stores a printable version of the 1st address + * given in the 'ai' argument. The result will be stored in the buf that is + * bufsize bytes big. + * + * If the conversion fails, the target buffer is empty. + */ +void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf, + size_t bufsize) +{ + DEBUGASSERT(bufsize); + buf[0] = 0; + + switch(ai->ai_family) { + case AF_INET: { + const struct sockaddr_in *sa4 = (const void *)ai->ai_addr; + const struct in_addr *ipaddr4 = &sa4->sin_addr; + (void)Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf, bufsize); + break; + } +#ifdef ENABLE_IPV6 + case AF_INET6: { + const struct sockaddr_in6 *sa6 = (const void *)ai->ai_addr; + const struct in6_addr *ipaddr6 = &sa6->sin6_addr; + (void)Curl_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf, bufsize); + break; + } +#endif + default: + break; + } +} + +/* + * 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_hostcache_id(const char *name, + size_t nlen, /* 0 or actual name length */ + int port, char *ptr, size_t buflen) +{ + size_t len = nlen ? nlen : strlen(name); + size_t olen = 0; + DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN); + if(len > (buflen - 7)) + len = buflen - 7; + /* store and lower case the name */ + while(len--) { + *ptr++ = Curl_raw_tolower(*name++); + olen++; + } + olen += msnprintf(ptr, 7, ":%u", port); + return olen; +} + +struct hostcache_prune_data { + time_t now; + time_t oldest; /* oldest time in cache not pruned. */ + int cache_timeout; +}; + +/* + * This function is set as a callback to be called for every entry in the DNS + * cache when we want to prune old unused entries. + * + * Returning non-zero means remove the entry, return 0 to keep it in the + * cache. + */ +static int +hostcache_timestamp_remove(void *datap, void *hc) +{ + struct hostcache_prune_data *prune = + (struct hostcache_prune_data *) datap; + struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc; + + if(c->timestamp) { + /* age in seconds */ + time_t age = prune->now - c->timestamp; + if(age >= prune->cache_timeout) + return TRUE; + if(age > prune->oldest) + prune->oldest = age; + } + return FALSE; +} + +/* + * Prune the DNS cache. This assumes that a lock has already been taken. + * Returns the 'age' of the oldest still kept entry. + */ +static time_t +hostcache_prune(struct Curl_hash *hostcache, int cache_timeout, + time_t now) +{ + struct hostcache_prune_data user; + + user.cache_timeout = cache_timeout; + user.now = now; + user.oldest = 0; + + Curl_hash_clean_with_criterium(hostcache, + (void *) &user, + hostcache_timestamp_remove); + + return user.oldest; +} + +/* + * Library-wide function for pruning the DNS cache. This function takes and + * returns the appropriate locks. + */ +void Curl_hostcache_prune(struct Curl_easy *data) +{ + time_t now; + /* the timeout may be set -1 (forever) */ + int timeout = data->set.dns_cache_timeout; + + if(!data->dns.hostcache) + /* NULL hostcache means we can't do it */ + return; + + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + time(&now); + + do { + /* Remove outdated and unused entries from the hostcache */ + time_t oldest = hostcache_prune(data->dns.hostcache, timeout, now); + + if(oldest < INT_MAX) + timeout = (int)oldest; /* we know it fits */ + else + timeout = INT_MAX - 1; + + /* if the cache size is still too big, use the oldest age as new + prune limit */ + } while(timeout && (data->dns.hostcache->size > MAX_DNS_CACHE_SIZE)); + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); +} + +#ifdef USE_ALARM_TIMEOUT +/* Beware this is a global and unique instance. This is used to store the + return address that we can jump back to from inside a signal handler. This + is not thread-safe stuff. */ +static sigjmp_buf curl_jmpenv; +static curl_simple_lock curl_jmpenv_lock; +#endif + +/* lookup address, returns entry if found and not stale */ +static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, + const char *hostname, + int port) +{ + struct Curl_dns_entry *dns = NULL; + char entry_id[MAX_HOSTCACHE_LEN]; + + /* Create an entry id, based upon the hostname and port */ + size_t entry_len = create_hostcache_id(hostname, 0, port, + entry_id, sizeof(entry_id)); + + /* See if its already in our dns cache */ + dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); + + /* No entry found in cache, check if we might have a wildcard entry */ + if(!dns && data->state.wildcard_resolve) { + entry_len = create_hostcache_id("*", 1, port, entry_id, sizeof(entry_id)); + + /* See if it's already in our dns cache */ + dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); + } + + if(dns && (data->set.dns_cache_timeout != -1)) { + /* See whether the returned entry is stale. Done before we release lock */ + struct hostcache_prune_data user; + + time(&user.now); + user.cache_timeout = data->set.dns_cache_timeout; + user.oldest = 0; + + if(hostcache_timestamp_remove(&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(data->dns.hostcache, entry_id, entry_len + 1); + } + } + + /* See if the returned entry matches the required resolve mode */ + if(dns && data->conn->ip_version != CURL_IPRESOLVE_WHATEVER) { + int pf = PF_INET; + bool found = false; + struct Curl_addrinfo *addr = dns->addr; + +#ifdef PF_INET6 + if(data->conn->ip_version == CURL_IPRESOLVE_V6) + pf = PF_INET6; +#endif + + while(addr) { + if(addr->ai_family == pf) { + found = true; + break; + } + addr = addr->ai_next; + } + + if(!found) { + infof(data, "Hostname in DNS cache doesn't have needed family, zapped"); + dns = NULL; /* the memory deallocation is being handled by the hash */ + Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); + } + } + return dns; +} + +/* + * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache. + * + * Curl_resolv() checks initially and multi_runsingle() checks each time + * it discovers the handle in the state WAITRESOLVE whether the hostname + * has already been resolved and the address has already been stored in + * the DNS cache. This short circuits waiting for a lot of pending + * lookups for the same hostname requested by different handles. + * + * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. + * + * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after + * use, or we'll leak memory! + */ +struct Curl_dns_entry * +Curl_fetch_addr(struct Curl_easy *data, + const char *hostname, + int port) +{ + struct Curl_dns_entry *dns = NULL; + + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + dns = fetch_addr(data, hostname, port); + + if(dns) + dns->inuse++; /* we use it! */ + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + + return dns; +} + +#ifndef CURL_DISABLE_SHUFFLE_DNS +/* + * Return # of addresses in a Curl_addrinfo struct + */ +static int num_addresses(const struct Curl_addrinfo *addr) +{ + int i = 0; + while(addr) { + addr = addr->ai_next; + i++; + } + return i; +} + +UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, + struct Curl_addrinfo **addr); +/* + * Curl_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo' + * struct by re-linking its linked list. + * + * The addr argument should be the address of a pointer to the head node of a + * `Curl_addrinfo` list and it will be modified to point to the new head after + * shuffling. + * + * Not declared static only to make it easy to use in a unit test! + * + * @unittest: 1608 + */ +UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, + struct Curl_addrinfo **addr) +{ + CURLcode result = CURLE_OK; + const int num_addrs = num_addresses(*addr); + + if(num_addrs > 1) { + struct Curl_addrinfo **nodes; + infof(data, "Shuffling %i addresses", num_addrs); + + nodes = malloc(num_addrs*sizeof(*nodes)); + if(nodes) { + int i; + unsigned int *rnd; + const size_t rnd_size = num_addrs * sizeof(*rnd); + + /* build a plain array of Curl_addrinfo pointers */ + nodes[0] = *addr; + for(i = 1; i < num_addrs; i++) { + nodes[i] = nodes[i-1]->ai_next; + } + + rnd = malloc(rnd_size); + if(rnd) { + /* Fisher-Yates shuffle */ + if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) { + struct Curl_addrinfo *swap_tmp; + for(i = num_addrs - 1; i > 0; i--) { + swap_tmp = nodes[rnd[i] % (i + 1)]; + nodes[rnd[i] % (i + 1)] = nodes[i]; + nodes[i] = swap_tmp; + } + + /* relink list in the new order */ + for(i = 1; i < num_addrs; i++) { + nodes[i-1]->ai_next = nodes[i]; + } + + nodes[num_addrs-1]->ai_next = NULL; + *addr = nodes[0]; + } + free(rnd); + } + else + result = CURLE_OUT_OF_MEMORY; + free(nodes); + } + else + result = CURLE_OUT_OF_MEMORY; + } + return result; +} +#endif + +/* + * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. + * + * When calling Curl_resolv() has resulted in a response with a returned + * address, we call this function to store the information in the dns + * cache etc + * + * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. + */ +struct Curl_dns_entry * +Curl_cache_addr(struct Curl_easy *data, + struct Curl_addrinfo *addr, + const char *hostname, + size_t hostlen, /* length or zero */ + int port) +{ + char entry_id[MAX_HOSTCACHE_LEN]; + size_t entry_len; + struct Curl_dns_entry *dns; + struct Curl_dns_entry *dns2; + +#ifndef CURL_DISABLE_SHUFFLE_DNS + /* shuffle addresses if requested */ + if(data->set.dns_shuffle_addresses) { + CURLcode result = Curl_shuffle_addr(data, &addr); + if(result) + return NULL; + } +#endif + if(!hostlen) + hostlen = strlen(hostname); + + /* Create a new cache entry */ + dns = calloc(1, sizeof(struct Curl_dns_entry) + hostlen); + if(!dns) { + return NULL; + } + + /* Create an entry id, based upon the hostname and port */ + entry_len = create_hostcache_id(hostname, hostlen, port, + entry_id, sizeof(entry_id)); + + dns->inuse = 1; /* the cache has the first reference */ + dns->addr = addr; /* this is the address(es) */ + time(&dns->timestamp); + if(dns->timestamp == 0) + dns->timestamp = 1; /* zero indicates permanent CURLOPT_RESOLVE entry */ + dns->hostport = port; + if(hostlen) + memcpy(dns->hostname, hostname, hostlen); + + /* Store the resolved data in our DNS cache. */ + dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len + 1, + (void *)dns); + if(!dns2) { + free(dns); + return NULL; + } + + dns = dns2; + dns->inuse++; /* mark entry as in-use */ + return dns; +} + +#ifdef ENABLE_IPV6 +/* return a static IPv6 ::1 for the name */ +static struct Curl_addrinfo *get_localhost6(int port, const char *name) +{ + struct Curl_addrinfo *ca; + const size_t ss_size = sizeof(struct sockaddr_in6); + const size_t hostlen = strlen(name); + struct sockaddr_in6 sa6; + unsigned char ipv6[16]; + unsigned short port16 = (unsigned short)(port & 0xffff); + ca = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1); + if(!ca) + return NULL; + + sa6.sin6_family = AF_INET6; + sa6.sin6_port = htons(port16); + sa6.sin6_flowinfo = 0; + sa6.sin6_scope_id = 0; + if(Curl_inet_pton(AF_INET6, "::1", ipv6) < 1) + return NULL; + memcpy(&sa6.sin6_addr, ipv6, sizeof(ipv6)); + + ca->ai_flags = 0; + ca->ai_family = AF_INET6; + ca->ai_socktype = SOCK_STREAM; + ca->ai_protocol = IPPROTO_TCP; + ca->ai_addrlen = (curl_socklen_t)ss_size; + ca->ai_next = NULL; + ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo)); + memcpy(ca->ai_addr, &sa6, ss_size); + ca->ai_canonname = (char *)ca->ai_addr + ss_size; + strcpy(ca->ai_canonname, name); + return ca; +} +#else +#define get_localhost6(x,y) NULL +#endif + +/* return a static IPv4 127.0.0.1 for the given name */ +static struct Curl_addrinfo *get_localhost(int port, const char *name) +{ + struct Curl_addrinfo *ca; + struct Curl_addrinfo *ca6; + const size_t ss_size = sizeof(struct sockaddr_in); + const size_t hostlen = strlen(name); + struct sockaddr_in sa; + unsigned int ipv4; + unsigned short port16 = (unsigned short)(port & 0xffff); + + /* memset to clear the sa.sin_zero field */ + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(port16); + if(Curl_inet_pton(AF_INET, "127.0.0.1", (char *)&ipv4) < 1) + return NULL; + memcpy(&sa.sin_addr, &ipv4, sizeof(ipv4)); + + ca = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1); + if(!ca) + return NULL; + ca->ai_flags = 0; + ca->ai_family = AF_INET; + ca->ai_socktype = SOCK_STREAM; + ca->ai_protocol = IPPROTO_TCP; + ca->ai_addrlen = (curl_socklen_t)ss_size; + ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo)); + memcpy(ca->ai_addr, &sa, ss_size); + ca->ai_canonname = (char *)ca->ai_addr + ss_size; + strcpy(ca->ai_canonname, name); + + ca6 = get_localhost6(port, name); + if(!ca6) + return ca; + ca6->ai_next = ca; + return ca6; +} + +#ifdef ENABLE_IPV6 +/* + * Curl_ipv6works() returns TRUE if IPv6 seems to work. + */ +bool Curl_ipv6works(struct Curl_easy *data) +{ + if(data) { + /* the nature of most system is that IPv6 status doesn't come and go + during a program's lifetime so we only probe the first time and then we + have the info kept for fast reuse */ + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + if(data->multi->ipv6_up == IPV6_UNKNOWN) { + bool works = Curl_ipv6works(NULL); + data->multi->ipv6_up = works ? IPV6_WORKS : IPV6_DEAD; + } + return data->multi->ipv6_up == IPV6_WORKS; + } + else { + int ipv6_works = -1; + /* probe to see if we have a working IPv6 stack */ + curl_socket_t s = socket(PF_INET6, SOCK_DGRAM, 0); + if(s == CURL_SOCKET_BAD) + /* an IPv6 address was requested but we can't get/use one */ + ipv6_works = 0; + else { + ipv6_works = 1; + sclose(s); + } + return (ipv6_works>0)?TRUE:FALSE; + } +} +#endif /* ENABLE_IPV6 */ + +/* + * Curl_host_is_ipnum() returns TRUE if the given string is a numerical IPv4 + * (or IPv6 if supported) address. + */ +bool Curl_host_is_ipnum(const char *hostname) +{ + struct in_addr in; +#ifdef ENABLE_IPV6 + struct in6_addr in6; +#endif + if(Curl_inet_pton(AF_INET, hostname, &in) > 0 +#ifdef ENABLE_IPV6 + || Curl_inet_pton(AF_INET6, hostname, &in6) > 0 +#endif + ) + return TRUE; + return FALSE; +} + + +/* return TRUE if 'part' is a case insensitive tail of 'full' */ +static bool tailmatch(const char *full, const char *part) +{ + size_t plen = strlen(part); + size_t flen = strlen(full); + if(plen > flen) + return FALSE; + return strncasecompare(part, &full[flen - plen], plen); +} + +/* + * Curl_resolv() is the main name resolve function within libcurl. It resolves + * a name and returns a pointer to the entry in the 'entry' argument (if one + * is provided). This function might return immediately if we're using asynch + * resolves. See the return codes. + * + * The cache entry we return will get its 'inuse' counter increased when this + * function is used. You MUST call Curl_resolv_unlock() later (when you're + * done using this struct) to decrease the counter again. + * + * Return codes: + * + * CURLRESOLV_ERROR (-1) = error, no pointer + * CURLRESOLV_RESOLVED (0) = OK, pointer provided + * CURLRESOLV_PENDING (1) = waiting for response, no pointer + */ + +enum resolve_t Curl_resolv(struct Curl_easy *data, + const char *hostname, + int port, + bool allowDOH, + struct Curl_dns_entry **entry) +{ + struct Curl_dns_entry *dns = NULL; + CURLcode result; + enum resolve_t rc = CURLRESOLV_ERROR; /* default to failure */ + struct connectdata *conn = data->conn; + /* We should intentionally error and not resolve .onion TLDs */ + size_t hostname_len = strlen(hostname); + if(hostname_len >= 7 && + (curl_strequal(&hostname[hostname_len - 6], ".onion") || + curl_strequal(&hostname[hostname_len - 7], ".onion."))) { + failf(data, "Not resolving .onion address (RFC 7686)"); + return CURLRESOLV_ERROR; + } + *entry = NULL; +#ifndef CURL_DISABLE_DOH + conn->bits.doh = FALSE; /* default is not */ +#else + (void)allowDOH; +#endif + + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + dns = fetch_addr(data, hostname, port); + + if(dns) { + infof(data, "Hostname %s was found in DNS cache", hostname); + dns->inuse++; /* we use it! */ + rc = CURLRESOLV_RESOLVED; + } + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + + if(!dns) { + /* The entry was not in the cache. Resolve it to IP address */ + + struct Curl_addrinfo *addr = NULL; + int respwait = 0; +#if !defined(CURL_DISABLE_DOH) || !defined(USE_RESOLVE_ON_IPS) + struct in_addr in; +#endif +#ifndef CURL_DISABLE_DOH +#ifndef USE_RESOLVE_ON_IPS + const +#endif + bool ipnum = FALSE; +#endif + + /* notify the resolver start callback */ + if(data->set.resolver_start) { + int st; + Curl_set_in_callback(data, true); + st = data->set.resolver_start( +#ifdef USE_CURL_ASYNC + conn->resolve_async.resolver, +#else + NULL, +#endif + NULL, + data->set.resolver_start_client); + Curl_set_in_callback(data, false); + if(st) + return CURLRESOLV_ERROR; + } + +#ifndef USE_RESOLVE_ON_IPS + /* First check if this is an IPv4 address string */ + if(Curl_inet_pton(AF_INET, hostname, &in) > 0) + /* This is a dotted IP address 123.123.123.123-style */ + addr = Curl_ip2addr(AF_INET, &in, hostname, port); +#ifdef ENABLE_IPV6 + if(!addr) { + struct in6_addr in6; + /* check if this is an IPv6 address string */ + if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) + /* This is an IPv6 address literal */ + addr = Curl_ip2addr(AF_INET6, &in6, hostname, port); + } +#endif /* ENABLE_IPV6 */ + +#else /* if USE_RESOLVE_ON_IPS */ +#ifndef CURL_DISABLE_DOH + /* First check if this is an IPv4 address string */ + if(Curl_inet_pton(AF_INET, hostname, &in) > 0) + /* This is a dotted IP address 123.123.123.123-style */ + ipnum = TRUE; +#ifdef ENABLE_IPV6 + else { + struct in6_addr in6; + /* check if this is an IPv6 address string */ + if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) + /* This is an IPv6 address literal */ + ipnum = TRUE; + } +#endif /* ENABLE_IPV6 */ +#endif /* CURL_DISABLE_DOH */ + +#endif /* !USE_RESOLVE_ON_IPS */ + + if(!addr) { + if(conn->ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data)) + return CURLRESOLV_ERROR; + + if(strcasecompare(hostname, "localhost") || + tailmatch(hostname, ".localhost")) + addr = get_localhost(port, hostname); +#ifndef CURL_DISABLE_DOH + else if(allowDOH && data->set.doh && !ipnum) + addr = Curl_doh(data, hostname, port, &respwait); +#endif + else { + /* Check what IP specifics the app has requested and if we can provide + * it. If not, bail out. */ + if(!Curl_ipvalid(data, conn)) + return CURLRESOLV_ERROR; + /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a + non-zero value indicating that we need to wait for the response to + the resolve call */ + addr = Curl_getaddrinfo(data, hostname, port, &respwait); + } + } + if(!addr) { + if(respwait) { + /* the response to our resolve call will come asynchronously at + a later time, good or bad */ + /* First, check that we haven't received the info by now */ + result = Curl_resolv_check(data, &dns); + if(result) /* error detected */ + return CURLRESOLV_ERROR; + if(dns) + rc = CURLRESOLV_RESOLVED; /* pointer provided */ + else + rc = CURLRESOLV_PENDING; /* no info yet */ + } + } + else { + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + /* we got a response, store it in the cache */ + dns = Curl_cache_addr(data, addr, hostname, 0, port); + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + + if(!dns) + /* returned failure, bail out nicely */ + Curl_freeaddrinfo(addr); + else { + rc = CURLRESOLV_RESOLVED; + show_resolve_info(data, dns); + } + } + } + + *entry = dns; + + return rc; +} + +#ifdef USE_ALARM_TIMEOUT +/* + * This signal handler jumps back into the main libcurl code and continues + * execution. This effectively causes the remainder of the application to run + * within a signal handler which is nonportable and could lead to problems. + */ +CURL_NORETURN static +void alarmfunc(int sig) +{ + (void)sig; + siglongjmp(curl_jmpenv, 1); +} +#endif /* USE_ALARM_TIMEOUT */ + +/* + * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a + * timeout. This function might return immediately if we're using asynch + * resolves. See the return codes. + * + * The cache entry we return will get its 'inuse' counter increased when this + * function is used. You MUST call Curl_resolv_unlock() later (when you're + * done using this struct) to decrease the counter again. + * + * If built with a synchronous resolver and use of signals is not + * disabled by the application, then a nonzero timeout will cause a + * timeout after the specified number of milliseconds. Otherwise, timeout + * is ignored. + * + * Return codes: + * + * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired + * CURLRESOLV_ERROR (-1) = error, no pointer + * CURLRESOLV_RESOLVED (0) = OK, pointer provided + * CURLRESOLV_PENDING (1) = waiting for response, no pointer + */ + +enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, + const char *hostname, + int port, + struct Curl_dns_entry **entry, + timediff_t timeoutms) +{ +#ifdef USE_ALARM_TIMEOUT +#ifdef HAVE_SIGACTION + struct sigaction keep_sigact; /* store the old struct here */ + volatile bool keep_copysig = FALSE; /* whether old sigact has been saved */ + struct sigaction sigact; +#else +#ifdef HAVE_SIGNAL + void (*keep_sigact)(int); /* store the old handler here */ +#endif /* HAVE_SIGNAL */ +#endif /* HAVE_SIGACTION */ + volatile long timeout; + volatile unsigned int prev_alarm = 0; +#endif /* USE_ALARM_TIMEOUT */ + enum resolve_t rc; + + *entry = NULL; + + if(timeoutms < 0) + /* got an already expired timeout */ + return CURLRESOLV_TIMEDOUT; + +#ifdef USE_ALARM_TIMEOUT + if(data->set.no_signal) + /* Ignore the timeout when signals are disabled */ + timeout = 0; + else + timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms; + + if(!timeout) + /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */ + return Curl_resolv(data, hostname, port, TRUE, entry); + + if(timeout < 1000) { + /* The alarm() function only provides integer second resolution, so if + we want to wait less than one second we must bail out already now. */ + failf(data, + "remaining timeout of %ld too small to resolve via SIGALRM method", + timeout); + return CURLRESOLV_TIMEDOUT; + } + /* This allows us to time-out from the name resolver, as the timeout + will generate a signal and we will siglongjmp() from that here. + This technique has problems (see alarmfunc). + This should be the last thing we do before calling Curl_resolv(), + as otherwise we'd have to worry about variables that get modified + before we invoke Curl_resolv() (and thus use "volatile"). */ + curl_simple_lock_lock(&curl_jmpenv_lock); + + if(sigsetjmp(curl_jmpenv, 1)) { + /* this is coming from a siglongjmp() after an alarm signal */ + failf(data, "name lookup timed out"); + rc = CURLRESOLV_ERROR; + goto clean_up; + } + else { + /************************************************************* + * Set signal handler to catch SIGALRM + * Store the old value to be able to set it back later! + *************************************************************/ +#ifdef HAVE_SIGACTION + sigaction(SIGALRM, NULL, &sigact); + keep_sigact = sigact; + keep_copysig = TRUE; /* yes, we have a copy */ + sigact.sa_handler = alarmfunc; +#ifdef SA_RESTART + /* HPUX doesn't have SA_RESTART but defaults to that behavior! */ + sigact.sa_flags &= ~SA_RESTART; +#endif + /* now set the new struct */ + sigaction(SIGALRM, &sigact, NULL); +#else /* HAVE_SIGACTION */ + /* no sigaction(), revert to the much lamer signal() */ +#ifdef HAVE_SIGNAL + keep_sigact = signal(SIGALRM, alarmfunc); +#endif +#endif /* HAVE_SIGACTION */ + + /* alarm() makes a signal get sent when the timeout fires off, and that + will abort system calls */ + prev_alarm = alarm(curlx_sltoui(timeout/1000L)); + } + +#else +#ifndef CURLRES_ASYNCH + if(timeoutms) + infof(data, "timeout on name lookup is not supported"); +#else + (void)timeoutms; /* timeoutms not used with an async resolver */ +#endif +#endif /* USE_ALARM_TIMEOUT */ + + /* Perform the actual name resolution. This might be interrupted by an + * alarm if it takes too long. + */ + rc = Curl_resolv(data, hostname, port, TRUE, entry); + +#ifdef USE_ALARM_TIMEOUT +clean_up: + + if(!prev_alarm) + /* deactivate a possibly active alarm before uninstalling the handler */ + alarm(0); + +#ifdef HAVE_SIGACTION + if(keep_copysig) { + /* we got a struct as it looked before, now put that one back nice + and clean */ + sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */ + } +#else +#ifdef HAVE_SIGNAL + /* restore the previous SIGALRM handler */ + signal(SIGALRM, keep_sigact); +#endif +#endif /* HAVE_SIGACTION */ + + curl_simple_lock_unlock(&curl_jmpenv_lock); + + /* switch back the alarm() to either zero or to what it was before minus + the time we spent until now! */ + if(prev_alarm) { + /* there was an alarm() set before us, now put it back */ + timediff_t elapsed_secs = Curl_timediff(Curl_now(), + data->conn->created) / 1000; + + /* the alarm period is counted in even number of seconds */ + unsigned long alarm_set = (unsigned long)(prev_alarm - elapsed_secs); + + if(!alarm_set || + ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) { + /* if the alarm time-left reached zero or turned "negative" (counted + with unsigned values), we should fire off a SIGALRM here, but we + won't, and zero would be to switch it off so we never set it to + less than 1! */ + alarm(1); + rc = CURLRESOLV_TIMEDOUT; + failf(data, "Previous alarm fired off"); + } + else + alarm((unsigned int)alarm_set); + } +#endif /* USE_ALARM_TIMEOUT */ + + return rc; +} + +/* + * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been + * made, the struct may be destroyed due to pruning. It is important that only + * one unlock is made for each Curl_resolv() call. + * + * May be called with 'data' == NULL for global cache. + */ +void Curl_resolv_unlock(struct Curl_easy *data, struct Curl_dns_entry *dns) +{ + if(data && data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + freednsentry(dns); + + if(data && data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); +} + +/* + * File-internal: release cache dns entry reference, free if inuse drops to 0 + */ +static void freednsentry(void *freethis) +{ + struct Curl_dns_entry *dns = (struct Curl_dns_entry *) freethis; + DEBUGASSERT(dns && (dns->inuse>0)); + + dns->inuse--; + if(dns->inuse == 0) { + Curl_freeaddrinfo(dns->addr); + free(dns); + } +} + +/* + * Curl_init_dnscache() inits a new DNS cache. + */ +void Curl_init_dnscache(struct Curl_hash *hash, int size) +{ + Curl_hash_init(hash, size, Curl_hash_str, Curl_str_key_compare, + freednsentry); +} + +/* + * Curl_hostcache_clean() + * + * This _can_ be called with 'data' == NULL but then of course no locking + * can be done! + */ + +void Curl_hostcache_clean(struct Curl_easy *data, + struct Curl_hash *hash) +{ + if(data && data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + Curl_hash_clean(hash); + + if(data && data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); +} + + +CURLcode Curl_loadhostpairs(struct Curl_easy *data) +{ + struct curl_slist *hostp; + char *host_end; + + /* Default is no wildcard found */ + data->state.wildcard_resolve = false; + + for(hostp = data->state.resolve; hostp; hostp = hostp->next) { + char entry_id[MAX_HOSTCACHE_LEN]; + if(!hostp->data) + continue; + if(hostp->data[0] == '-') { + unsigned long num = 0; + size_t entry_len; + size_t hlen = 0; + host_end = strchr(&hostp->data[1], ':'); + + if(host_end) { + hlen = host_end - &hostp->data[1]; + num = strtoul(++host_end, NULL, 10); + if(!hlen || (num > 0xffff)) + host_end = NULL; + } + if(!host_end) { + infof(data, "Bad syntax CURLOPT_RESOLVE removal entry '%s'", + hostp->data); + continue; + } + /* Create an entry id, based upon the hostname and port */ + entry_len = create_hostcache_id(&hostp->data[1], hlen, (int)num, + entry_id, sizeof(entry_id)); + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + /* delete entry, ignore if it didn't exist */ + Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + } + else { + struct Curl_dns_entry *dns; + struct Curl_addrinfo *head = NULL, *tail = NULL; + size_t entry_len; + char address[64]; +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + char *addresses = NULL; +#endif + char *addr_begin; + char *addr_end; + char *port_ptr; + int port = 0; + char *end_ptr; + bool permanent = TRUE; + unsigned long tmp_port; + bool error = true; + char *host_begin = hostp->data; + size_t hlen = 0; + + if(host_begin[0] == '+') { + host_begin++; + permanent = FALSE; + } + host_end = strchr(host_begin, ':'); + if(!host_end) + goto err; + hlen = host_end - host_begin; + + port_ptr = host_end + 1; + tmp_port = strtoul(port_ptr, &end_ptr, 10); + if(tmp_port > USHRT_MAX || end_ptr == port_ptr || *end_ptr != ':') + goto err; + + port = (int)tmp_port; +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + addresses = end_ptr + 1; +#endif + + while(*end_ptr) { + size_t alen; + struct Curl_addrinfo *ai; + + addr_begin = end_ptr + 1; + addr_end = strchr(addr_begin, ','); + if(!addr_end) + addr_end = addr_begin + strlen(addr_begin); + end_ptr = addr_end; + + /* allow IP(v6) address within [brackets] */ + if(*addr_begin == '[') { + if(addr_end == addr_begin || *(addr_end - 1) != ']') + goto err; + ++addr_begin; + --addr_end; + } + + alen = addr_end - addr_begin; + if(!alen) + continue; + + if(alen >= sizeof(address)) + goto err; + + memcpy(address, addr_begin, alen); + address[alen] = '\0'; + +#ifndef ENABLE_IPV6 + if(strchr(address, ':')) { + infof(data, "Ignoring resolve address '%s', missing IPv6 support.", + address); + continue; + } +#endif + + ai = Curl_str2addr(address, port); + if(!ai) { + infof(data, "Resolve address '%s' found illegal", address); + goto err; + } + + if(tail) { + tail->ai_next = ai; + tail = tail->ai_next; + } + else { + head = tail = ai; + } + } + + if(!head) + goto err; + + error = false; +err: + if(error) { + failf(data, "Couldn't parse CURLOPT_RESOLVE entry '%s'", + hostp->data); + Curl_freeaddrinfo(head); + return CURLE_SETOPT_OPTION_SYNTAX; + } + + /* Create an entry id, based upon the hostname and port */ + entry_len = create_hostcache_id(host_begin, hlen, port, + entry_id, sizeof(entry_id)); + + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + /* See if it's already in our dns cache */ + dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); + + if(dns) { + infof(data, "RESOLVE %.*s:%d - old addresses discarded", + (int)hlen, host_begin, 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, + but if it is close to expire, then by the time next http + request is made, it can get expired and pruned because old + entry is not necessarily marked as permanent. + 3. when adding a non-permanent entry, we want it to remove and + replace an existing permanent entry. + 4. when adding a non-permanent entry, we want it to get a "fresh" + timeout that starts _now_. */ + + Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); + } + + /* put this new host in the cache */ + dns = Curl_cache_addr(data, head, host_begin, hlen, port); + if(dns) { + if(permanent) + dns->timestamp = 0; /* mark as permanent */ + /* release the returned reference; the cache itself will keep the + * entry alive: */ + dns->inuse--; + } + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + + if(!dns) { + Curl_freeaddrinfo(head); + return CURLE_OUT_OF_MEMORY; + } +#ifndef CURL_DISABLE_VERBOSE_STRINGS + infof(data, "Added %.*s:%d:%s to DNS cache%s", + (int)hlen, host_begin, port, addresses, + permanent ? "" : " (non-permanent)"); +#endif + + /* Wildcard hostname */ + if((hlen == 1) && (host_begin[0] == '*')) { + infof(data, "RESOLVE *:%d using wildcard", port); + data->state.wildcard_resolve = true; + } + } + } + data->state.resolve = NULL; /* dealt with now */ + + return CURLE_OK; +} + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void show_resolve_info(struct Curl_easy *data, + struct Curl_dns_entry *dns) +{ + struct Curl_addrinfo *a; + CURLcode result = CURLE_OK; +#ifdef CURLRES_IPV6 + struct dynbuf out[2]; +#else + struct dynbuf out[1]; +#endif + DEBUGASSERT(data); + DEBUGASSERT(dns); + + if(!data->set.verbose || + /* ignore no name or numerical IP addresses */ + !dns->hostname[0] || Curl_host_is_ipnum(dns->hostname)) + return; + + a = dns->addr; + + infof(data, "Host %s:%d was resolved.", + (dns->hostname[0] ? dns->hostname : "(none)"), dns->hostport); + + Curl_dyn_init(&out[0], 1024); +#ifdef CURLRES_IPV6 + Curl_dyn_init(&out[1], 1024); +#endif + + while(a) { + if( +#ifdef CURLRES_IPV6 + a->ai_family == PF_INET6 || +#endif + a->ai_family == PF_INET) { + char buf[MAX_IPADR_LEN]; + struct dynbuf *d = &out[(a->ai_family != PF_INET)]; + Curl_printable_address(a, buf, sizeof(buf)); + if(Curl_dyn_len(d)) + result = Curl_dyn_addn(d, ", ", 2); + if(!result) + result = Curl_dyn_add(d, buf); + if(result) { + infof(data, "too many IP, can't show"); + goto fail; + } + } + a = a->ai_next; + } + +#ifdef CURLRES_IPV6 + infof(data, "IPv6: %s", + (Curl_dyn_len(&out[1]) ? Curl_dyn_ptr(&out[1]) : "(none)")); +#endif + infof(data, "IPv4: %s", + (Curl_dyn_len(&out[0]) ? Curl_dyn_ptr(&out[0]) : "(none)")); + +fail: + Curl_dyn_free(&out[0]); +#ifdef CURLRES_IPV6 + Curl_dyn_free(&out[1]); +#endif +} +#endif + +CURLcode Curl_resolv_check(struct Curl_easy *data, + struct Curl_dns_entry **dns) +{ + CURLcode result; +#if defined(CURL_DISABLE_DOH) && !defined(CURLRES_ASYNCH) + (void)data; + (void)dns; +#endif +#ifndef CURL_DISABLE_DOH + if(data->conn->bits.doh) { + result = Curl_doh_is_resolved(data, dns); + } + else +#endif + result = Curl_resolver_is_resolved(data, dns); + if(*dns) + show_resolve_info(data, *dns); + return result; +} + +int Curl_resolv_getsock(struct Curl_easy *data, + curl_socket_t *socks) +{ +#ifdef CURLRES_ASYNCH +#ifndef CURL_DISABLE_DOH + if(data->conn->bits.doh) + /* nothing to wait for during DoH resolve, those handles have their own + sockets */ + return GETSOCK_BLANK; +#endif + return Curl_resolver_getsock(data, socks); +#else + (void)data; + (void)socks; + return GETSOCK_BLANK; +#endif +} + +/* Call this function after Curl_connect() has returned async=TRUE and + then a successful name resolve has been received. + + Note: this function disconnects and frees the conn data in case of + resolve failure */ +CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done) +{ + CURLcode result; + struct connectdata *conn = data->conn; + +#ifdef USE_CURL_ASYNC + if(conn->resolve_async.dns) { + conn->dns_entry = conn->resolve_async.dns; + conn->resolve_async.dns = NULL; + } +#endif + + result = Curl_setup_conn(data, protocol_done); + + if(result) { + Curl_detach_connection(data); + Curl_conncache_remove_conn(data, conn, TRUE); + Curl_disconnect(data, conn, TRUE); + } + return result; +} + +/* + * Curl_resolver_error() calls failf() with the appropriate message after a + * resolve error + */ + +#ifdef USE_CURL_ASYNC +CURLcode Curl_resolver_error(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + const char *host_or_proxy; + CURLcode result; + +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy) { + host_or_proxy = "proxy"; + result = CURLE_COULDNT_RESOLVE_PROXY; + } + else +#endif + { + host_or_proxy = "host"; + result = CURLE_COULDNT_RESOLVE_HOST; + } + + failf(data, "Could not resolve %s: %s", host_or_proxy, + conn->resolve_async.hostname); + + return result; +} +#endif /* USE_CURL_ASYNC */ diff --git a/Utilities/cmcurl/lib/hostip.h b/Utilities/cmcurl/lib/hostip.h new file mode 100644 index 0000000..fb53a57 --- /dev/null +++ b/Utilities/cmcurl/lib/hostip.h @@ -0,0 +1,229 @@ +#ifndef HEADER_CURL_HOSTIP_H +#define HEADER_CURL_HOSTIP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "hash.h" +#include "curl_addrinfo.h" +#include "timeval.h" /* for timediff_t */ +#include "asyn.h" + +#include <setjmp.h> + +/* Allocate enough memory to hold the full name information structs and + * everything. OSF1 is known to require at least 8872 bytes. The buffer + * required for storing all possible aliases and IP numbers is according to + * Stevens' Unix Network Programming 2nd edition, p. 304: 8192 bytes! + */ +#define CURL_HOSTENT_SIZE 9000 + +#define CURL_TIMEOUT_RESOLVE 300 /* when using asynch methods, we allow this + many seconds for a name resolve */ + +#define CURL_ASYNC_SUCCESS CURLE_OK + +struct addrinfo; +struct hostent; +struct Curl_easy; +struct connectdata; + +/* + * Curl_global_host_cache_init() initializes and sets up a global DNS cache. + * Global DNS cache is general badness. Do not use. This will be removed in + * a future version. Use the share interface instead! + * + * Returns a struct Curl_hash pointer on success, NULL on failure. + */ +struct Curl_hash *Curl_global_host_cache_init(void); + +struct Curl_dns_entry { + struct Curl_addrinfo *addr; + /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (doesn't time out) */ + time_t timestamp; + /* use-counter, use Curl_resolv_unlock to release reference */ + long inuse; + /* hostname port number that resolved to addr. */ + int hostport; + /* hostname that resolved to addr. may be NULL (unix domain sockets). */ + char hostname[1]; +}; + +bool Curl_host_is_ipnum(const char *hostname); + +/* + * Curl_resolv() returns an entry with the info for the specified host + * and port. + * + * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after + * use, or we'll leak memory! + */ +/* return codes */ +enum resolve_t { + CURLRESOLV_TIMEDOUT = -2, + CURLRESOLV_ERROR = -1, + CURLRESOLV_RESOLVED = 0, + CURLRESOLV_PENDING = 1 +}; +enum resolve_t Curl_resolv(struct Curl_easy *data, + const char *hostname, + int port, + bool allowDOH, + struct Curl_dns_entry **dnsentry); +enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, + const char *hostname, int port, + struct Curl_dns_entry **dnsentry, + timediff_t timeoutms); + +#ifdef ENABLE_IPV6 +/* + * Curl_ipv6works() returns TRUE if IPv6 seems to work. + */ +bool Curl_ipv6works(struct Curl_easy *data); +#else +#define Curl_ipv6works(x) FALSE +#endif + +/* + * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've + * been set and returns TRUE if they are OK. + */ +bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn); + + +/* + * Curl_getaddrinfo() is the generic low-level name resolve API within this + * source file. There are several versions of this function - for different + * name resolve layers (selected at build-time). They all take this same set + * of arguments + */ +struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp); + + +/* unlock a previously resolved dns entry */ +void Curl_resolv_unlock(struct Curl_easy *data, + struct Curl_dns_entry *dns); + +/* init a new dns cache */ +void Curl_init_dnscache(struct Curl_hash *hash, int hashsize); + +/* prune old entries from the DNS cache */ +void Curl_hostcache_prune(struct Curl_easy *data); + +/* IPv4 threadsafe resolve function used for synch and asynch builds */ +struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port); + +CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_connect); + +/* + * Curl_addrinfo_callback() is used when we build with any asynch specialty. + * Handles end of async request processing. Inserts ai into hostcache when + * status is CURL_ASYNC_SUCCESS. Twiddles fields in conn to indicate async + * request completed whether successful or failed. + */ +CURLcode Curl_addrinfo_callback(struct Curl_easy *data, + int status, + struct Curl_addrinfo *ai); + +/* + * Curl_printable_address() returns a printable version of the 1st address + * given in the 'ip' argument. The result will be stored in the buf that is + * bufsize bytes big. + */ +void Curl_printable_address(const struct Curl_addrinfo *ip, + char *buf, size_t bufsize); + +/* + * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache. + * + * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. + * + * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after + * use, or we'll leak memory! + */ +struct Curl_dns_entry * +Curl_fetch_addr(struct Curl_easy *data, + const char *hostname, + int port); + +/* + * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. + * + * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. + */ +struct Curl_dns_entry * +Curl_cache_addr(struct Curl_easy *data, struct Curl_addrinfo *addr, + const char *hostname, size_t hostlen, int port); + +#ifndef INADDR_NONE +#define CURL_INADDR_NONE (in_addr_t) ~0 +#else +#define CURL_INADDR_NONE INADDR_NONE +#endif + +/* + * Function provided by the resolver backend to set DNS servers to use. + */ +CURLcode Curl_set_dns_servers(struct Curl_easy *data, char *servers); + +/* + * Function provided by the resolver backend to set + * outgoing interface to use for DNS requests + */ +CURLcode Curl_set_dns_interface(struct Curl_easy *data, + const char *interf); + +/* + * Function provided by the resolver backend to set + * local IPv4 address to use as source address for DNS requests + */ +CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, + const char *local_ip4); + +/* + * Function provided by the resolver backend to set + * local IPv6 address to use as source address for DNS requests + */ +CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, + const char *local_ip6); + +/* + * Clean off entries from the cache + */ +void Curl_hostcache_clean(struct Curl_easy *data, struct Curl_hash *hash); + +/* + * Populate the cache with specified entries from CURLOPT_RESOLVE. + */ +CURLcode Curl_loadhostpairs(struct Curl_easy *data); +CURLcode Curl_resolv_check(struct Curl_easy *data, + struct Curl_dns_entry **dns); +int Curl_resolv_getsock(struct Curl_easy *data, + curl_socket_t *socks); + +CURLcode Curl_resolver_error(struct Curl_easy *data); +#endif /* HEADER_CURL_HOSTIP_H */ diff --git a/Utilities/cmcurl/lib/hostip4.c b/Utilities/cmcurl/lib/hostip4.c new file mode 100644 index 0000000..9140180 --- /dev/null +++ b/Utilities/cmcurl/lib/hostip4.c @@ -0,0 +1,301 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +/*********************************************************************** + * Only for plain IPv4 builds + **********************************************************************/ +#ifdef CURLRES_IPV4 /* plain IPv4 code coming up */ + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "url.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've + * been set and returns TRUE if they are OK. + */ +bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) +{ + (void)data; + if(conn->ip_version == CURL_IPRESOLVE_V6) + /* An IPv6 address was requested and we can't get/use one */ + return FALSE; + + return TRUE; /* OK, proceed */ +} + +#ifdef CURLRES_SYNCH + +/* + * Curl_getaddrinfo() - the IPv4 synchronous version. + * + * The original code to this function was from the Dancer source code, written + * by Bjorn Reese, it has since been patched and modified considerably. + * + * gethostbyname_r() is the thread-safe version of the gethostbyname() + * function. When we build for plain IPv4, we attempt to use this + * function. There are _three_ different gethostbyname_r() versions, and we + * detect which one this platform supports in the configure script and set up + * the HAVE_GETHOSTBYNAME_R_3, HAVE_GETHOSTBYNAME_R_5 or + * HAVE_GETHOSTBYNAME_R_6 defines accordingly. Note that HAVE_GETADDRBYNAME + * has the corresponding rules. This is primarily on *nix. Note that some unix + * flavours have thread-safe versions of the plain gethostbyname() etc. + * + */ +struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) +{ + struct Curl_addrinfo *ai = NULL; + +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)data; +#endif + + *waitp = 0; /* synchronous response only */ + + ai = Curl_ipv4_resolve_r(hostname, port); + if(!ai) + infof(data, "Curl_ipv4_resolve_r failed for %s", hostname); + + return ai; +} +#endif /* CURLRES_SYNCH */ +#endif /* CURLRES_IPV4 */ + +#if defined(CURLRES_IPV4) && \ + !defined(CURLRES_ARES) && !defined(CURLRES_AMIGA) + +/* + * Curl_ipv4_resolve_r() - ipv4 threadsafe resolver function. + * + * This is used for both synchronous and asynchronous resolver builds, + * implying that only threadsafe code and function calls may be used. + * + */ +struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, + int port) +{ +#if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) && \ + defined(HAVE_GETHOSTBYNAME_R_3) + int res; +#endif + struct Curl_addrinfo *ai = NULL; + struct hostent *h = NULL; + struct hostent *buf = NULL; + +#if defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE) + struct addrinfo hints; + char sbuf[12]; + char *sbufptr = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_INET; + hints.ai_socktype = SOCK_STREAM; + if(port) { + msnprintf(sbuf, sizeof(sbuf), "%d", port); + sbufptr = sbuf; + } + + (void)Curl_getaddrinfo_ex(hostname, sbufptr, &hints, &ai); + +#elif defined(HAVE_GETHOSTBYNAME_R) + /* + * gethostbyname_r() is the preferred resolve function for many platforms. + * Since there are three different versions of it, the following code is + * somewhat #ifdef-ridden. + */ + int h_errnop; + + buf = calloc(1, CURL_HOSTENT_SIZE); + if(!buf) + return NULL; /* major failure */ + /* + * The clearing of the buffer is a workaround for a gethostbyname_r bug in + * qnx nto and it is also _required_ for some of these functions on some + * platforms. + */ + +#if defined(HAVE_GETHOSTBYNAME_R_5) + /* Solaris, IRIX and more */ + h = gethostbyname_r(hostname, + (struct hostent *)buf, + (char *)buf + sizeof(struct hostent), + CURL_HOSTENT_SIZE - sizeof(struct hostent), + &h_errnop); + + /* If the buffer is too small, it returns NULL and sets errno to + * ERANGE. The errno is thread safe if this is compiled with + * -D_REENTRANT as then the 'errno' variable is a macro defined to get + * used properly for threads. + */ + + if(h) { + ; + } + else +#elif defined(HAVE_GETHOSTBYNAME_R_6) + /* Linux */ + + (void)gethostbyname_r(hostname, + (struct hostent *)buf, + (char *)buf + sizeof(struct hostent), + CURL_HOSTENT_SIZE - sizeof(struct hostent), + &h, /* DIFFERENCE */ + &h_errnop); + /* Redhat 8, using glibc 2.2.93 changed the behavior. Now all of a + * sudden this function returns EAGAIN if the given buffer size is too + * small. Previous versions are known to return ERANGE for the same + * problem. + * + * This wouldn't be such a big problem if older versions wouldn't + * sometimes return EAGAIN on a common failure case. Alas, we can't + * assume that EAGAIN *or* ERANGE means ERANGE for any given version of + * glibc. + * + * For now, we do that and thus we may call the function repeatedly and + * fail for older glibc versions that return EAGAIN, until we run out of + * buffer size (step_size grows beyond CURL_HOSTENT_SIZE). + * + * If anyone has a better fix, please tell us! + * + * ------------------------------------------------------------------- + * + * On October 23rd 2003, Dan C dug up more details on the mysteries of + * gethostbyname_r() in glibc: + * + * In glibc 2.2.5 the interface is different (this has also been + * discovered in glibc 2.1.1-6 as shipped by Redhat 6). What I can't + * explain, is that tests performed on glibc 2.2.4-34 and 2.2.4-32 + * (shipped/upgraded by Redhat 7.2) don't show this behavior! + * + * In this "buggy" version, the return code is -1 on error and 'errno' + * is set to the ERANGE or EAGAIN code. Note that 'errno' is not a + * thread-safe variable. + */ + + if(!h) /* failure */ +#elif defined(HAVE_GETHOSTBYNAME_R_3) + /* AIX, Digital Unix/Tru64, HPUX 10, more? */ + + /* For AIX 4.3 or later, we don't use gethostbyname_r() at all, because of + * the plain fact that it does not return unique full buffers on each + * call, but instead several of the pointers in the hostent structs will + * point to the same actual data! This have the unfortunate down-side that + * our caching system breaks down horribly. Luckily for us though, AIX 4.3 + * and more recent versions have a "completely thread-safe"[*] libc where + * all the data is stored in thread-specific memory areas making calls to + * the plain old gethostbyname() work fine even for multi-threaded + * programs. + * + * This AIX 4.3 or later detection is all made in the configure script. + * + * Troels Walsted Hansen helped us work this out on March 3rd, 2003. + * + * [*] = much later we've found out that it isn't at all "completely + * thread-safe", but at least the gethostbyname() function is. + */ + + if(CURL_HOSTENT_SIZE >= + (sizeof(struct hostent) + sizeof(struct hostent_data))) { + + /* August 22nd, 2000: Albert Chin-A-Young brought an updated version + * that should work! September 20: Richard Prescott worked on the buffer + * size dilemma. + */ + + res = gethostbyname_r(hostname, + (struct hostent *)buf, + (struct hostent_data *)((char *)buf + + sizeof(struct hostent))); + h_errnop = SOCKERRNO; /* we don't deal with this, but set it anyway */ + } + else + res = -1; /* failure, too smallish buffer size */ + + if(!res) { /* success */ + + h = buf; /* result expected in h */ + + /* This is the worst kind of the different gethostbyname_r() interfaces. + * Since we don't know how big buffer this particular lookup required, + * we can't realloc down the huge alloc without doing closer analysis of + * the returned data. Thus, we always use CURL_HOSTENT_SIZE for every + * name lookup. Fixing this would require an extra malloc() and then + * calling Curl_addrinfo_copy() that subsequent realloc()s down the new + * memory area to the actually used amount. + */ + } + else +#endif /* HAVE_...BYNAME_R_5 || HAVE_...BYNAME_R_6 || HAVE_...BYNAME_R_3 */ + { + h = NULL; /* set return code to NULL */ + free(buf); + } +#else /* (HAVE_GETADDRINFO && HAVE_GETADDRINFO_THREADSAFE) || + HAVE_GETHOSTBYNAME_R */ + /* + * Here is code for platforms that don't have a thread safe + * getaddrinfo() nor gethostbyname_r() function or for which + * gethostbyname() is the preferred one. + */ + h = gethostbyname((void *)hostname); +#endif /* (HAVE_GETADDRINFO && HAVE_GETADDRINFO_THREADSAFE) || + HAVE_GETHOSTBYNAME_R */ + + if(h) { + ai = Curl_he2ai(h, port); + + if(buf) /* used a *_r() function */ + free(buf); + } + + return ai; +} +#endif /* defined(CURLRES_IPV4) && !defined(CURLRES_ARES) && + !defined(CURLRES_AMIGA) */ diff --git a/Utilities/cmcurl/lib/hostip6.c b/Utilities/cmcurl/lib/hostip6.c new file mode 100644 index 0000000..18969a7 --- /dev/null +++ b/Utilities/cmcurl/lib/hostip6.c @@ -0,0 +1,157 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +/*********************************************************************** + * Only for IPv6-enabled builds + **********************************************************************/ +#ifdef CURLRES_IPV6 + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "url.h" +#include "inet_pton.h" +#include "connect.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've + * been set and returns TRUE if they are OK. + */ +bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) +{ + if(conn->ip_version == CURL_IPRESOLVE_V6) + return Curl_ipv6works(data); + + return TRUE; +} + +#if defined(CURLRES_SYNCH) + +#ifdef DEBUG_ADDRINFO +static void dump_addrinfo(const struct Curl_addrinfo *ai) +{ + printf("dump_addrinfo:\n"); + for(; ai; ai = ai->ai_next) { + char buf[INET6_ADDRSTRLEN]; + printf(" fam %2d, CNAME %s, ", + ai->ai_family, ai->ai_canonname ? ai->ai_canonname : "<none>"); + Curl_printable_address(ai, buf, sizeof(buf)); + printf("%s\n", buf); + } +} +#else +#define dump_addrinfo(x) Curl_nop_stmt +#endif + +/* + * Curl_getaddrinfo() when built IPv6-enabled (non-threading and + * non-ares version). + * + * Returns name information about the given hostname and port number. If + * successful, the 'addrinfo' is returned and the fourth argument will point + * to memory we need to free after use. That memory *MUST* be freed with + * Curl_freeaddrinfo(), nothing else. + */ +struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) +{ + struct addrinfo hints; + struct Curl_addrinfo *res; + int error; + char sbuf[12]; + char *sbufptr = NULL; +#ifndef USE_RESOLVE_ON_IPS + char addrbuf[128]; +#endif + int pf = PF_INET; + + *waitp = 0; /* synchronous response only */ + + if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) + /* The stack seems to be IPv6-enabled */ + pf = PF_UNSPEC; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = pf; + hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP) ? + SOCK_STREAM : SOCK_DGRAM; + +#ifndef USE_RESOLVE_ON_IPS + /* + * The AI_NUMERICHOST must not be set to get synthesized IPv6 address from + * an IPv4 address on iOS and Mac OS X. + */ + if((1 == Curl_inet_pton(AF_INET, hostname, addrbuf)) || + (1 == Curl_inet_pton(AF_INET6, hostname, addrbuf))) { + /* the given address is numerical only, prevent a reverse lookup */ + hints.ai_flags = AI_NUMERICHOST; + } +#endif + + if(port) { + msnprintf(sbuf, sizeof(sbuf), "%d", port); + sbufptr = sbuf; + } + + error = Curl_getaddrinfo_ex(hostname, sbufptr, &hints, &res); + if(error) { + infof(data, "getaddrinfo(3) failed for %s:%d", hostname, port); + return NULL; + } + + if(port) { + Curl_addrinfo_set_port(res, port); + } + + dump_addrinfo(res); + + return res; +} +#endif /* CURLRES_SYNCH */ + +#endif /* CURLRES_IPV6 */ diff --git a/Utilities/cmcurl/lib/hostsyn.c b/Utilities/cmcurl/lib/hostsyn.c new file mode 100644 index 0000000..ca8b075 --- /dev/null +++ b/Utilities/cmcurl/lib/hostsyn.c @@ -0,0 +1,104 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +/*********************************************************************** + * Only for builds using synchronous name resolves + **********************************************************************/ +#ifdef CURLRES_SYNCH + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "url.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/* + * Function provided by the resolver backend to set DNS servers to use. + */ +CURLcode Curl_set_dns_servers(struct Curl_easy *data, + char *servers) +{ + (void)data; + (void)servers; + return CURLE_NOT_BUILT_IN; + +} + +/* + * Function provided by the resolver backend to set + * outgoing interface to use for DNS requests + */ +CURLcode Curl_set_dns_interface(struct Curl_easy *data, + const char *interf) +{ + (void)data; + (void)interf; + return CURLE_NOT_BUILT_IN; +} + +/* + * Function provided by the resolver backend to set + * local IPv4 address to use as source address for DNS requests + */ +CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, + const char *local_ip4) +{ + (void)data; + (void)local_ip4; + return CURLE_NOT_BUILT_IN; +} + +/* + * Function provided by the resolver backend to set + * local IPv6 address to use as source address for DNS requests + */ +CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, + const char *local_ip6) +{ + (void)data; + (void)local_ip6; + return CURLE_NOT_BUILT_IN; +} + +#endif /* truly sync */ diff --git a/Utilities/cmcurl/lib/hsts.c b/Utilities/cmcurl/lib/hsts.c new file mode 100644 index 0000000..9314be2 --- /dev/null +++ b/Utilities/cmcurl/lib/hsts.c @@ -0,0 +1,587 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * The Strict-Transport-Security header is defined in RFC 6797: + * https://datatracker.ietf.org/doc/html/rfc6797 + */ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HSTS) +#include <curl/curl.h> +#include "urldata.h" +#include "llist.h" +#include "hsts.h" +#include "curl_get_line.h" +#include "strcase.h" +#include "sendf.h" +#include "strtoofft.h" +#include "parsedate.h" +#include "fopen.h" +#include "rename.h" +#include "share.h" +#include "strdup.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define MAX_HSTS_LINE 4095 +#define MAX_HSTS_HOSTLEN 256 +#define MAX_HSTS_HOSTLENSTR "256" +#define MAX_HSTS_DATELEN 64 +#define MAX_HSTS_DATELENSTR "64" +#define UNLIMITED "unlimited" + +#ifdef DEBUGBUILD +/* to play well with debug builds, we can *set* a fixed time this will + return */ +time_t deltatime; /* allow for "adjustments" for unit test purposes */ +static time_t hsts_debugtime(void *unused) +{ + char *timestr = getenv("CURL_TIME"); + (void)unused; + if(timestr) { + curl_off_t val; + (void)curlx_strtoofft(timestr, NULL, 10, &val); + + val += (curl_off_t)deltatime; + return (time_t)val; + } + return time(NULL); +} +#undef time +#define time(x) hsts_debugtime(x) +#endif + +struct hsts *Curl_hsts_init(void) +{ + struct hsts *h = calloc(1, sizeof(struct hsts)); + if(h) { + Curl_llist_init(&h->list, NULL); + } + return h; +} + +static void hsts_free(struct stsentry *e) +{ + free((char *)e->host); + free(e); +} + +void Curl_hsts_cleanup(struct hsts **hp) +{ + struct hsts *h = *hp; + if(h) { + struct Curl_llist_element *e; + struct Curl_llist_element *n; + for(e = h->list.head; e; e = n) { + struct stsentry *sts = e->ptr; + n = e->next; + hsts_free(sts); + } + free(h->filename); + free(h); + *hp = NULL; + } +} + +static struct stsentry *hsts_entry(void) +{ + return calloc(1, sizeof(struct stsentry)); +} + +static CURLcode hsts_create(struct hsts *h, + const char *hostname, + bool subdomains, + curl_off_t expires) +{ + struct stsentry *sts; + char *duphost; + size_t hlen; + DEBUGASSERT(h); + DEBUGASSERT(hostname); + + hlen = strlen(hostname); + if(hlen && (hostname[hlen - 1] == '.')) + /* strip off any trailing dot */ + --hlen; + if(!hlen) + /* no host name left */ + return CURLE_BAD_FUNCTION_ARGUMENT; + + sts = hsts_entry(); + if(!sts) + return CURLE_OUT_OF_MEMORY; + + duphost = Curl_strndup(hostname, hlen); + if(!duphost) { + free(sts); + return CURLE_OUT_OF_MEMORY; + } + + sts->host = duphost; + sts->expires = expires; + sts->includeSubDomains = subdomains; + Curl_llist_insert_next(&h->list, h->list.tail, sts, &sts->node); + return CURLE_OK; +} + +CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, + const char *header) +{ + const char *p = header; + curl_off_t expires = 0; + bool gotma = FALSE; + bool gotinc = FALSE; + bool subdomains = FALSE; + struct stsentry *sts; + time_t now = time(NULL); + + if(Curl_host_is_ipnum(hostname)) + /* "explicit IP address identification of all forms is excluded." + / RFC 6797 */ + return CURLE_OK; + + do { + while(*p && ISBLANK(*p)) + p++; + if(strncasecompare("max-age=", p, 8)) { + bool quoted = FALSE; + CURLofft offt; + char *endp; + + if(gotma) + return CURLE_BAD_FUNCTION_ARGUMENT; + + p += 8; + while(*p && ISBLANK(*p)) + p++; + if(*p == '\"') { + p++; + quoted = TRUE; + } + offt = curlx_strtoofft(p, &endp, 10, &expires); + if(offt == CURL_OFFT_FLOW) + expires = CURL_OFF_T_MAX; + else if(offt) + /* invalid max-age */ + return CURLE_BAD_FUNCTION_ARGUMENT; + p = endp; + if(quoted) { + if(*p != '\"') + return CURLE_BAD_FUNCTION_ARGUMENT; + p++; + } + gotma = TRUE; + } + else if(strncasecompare("includesubdomains", p, 17)) { + 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++; + } + + while(*p && ISBLANK(*p)) + p++; + if(*p == ';') + p++; + } while(*p); + + if(!gotma) + /* max-age is mandatory */ + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(!expires) { + /* remove the entry if present verbatim (without subdomain match) */ + sts = Curl_hsts(h, hostname, FALSE); + if(sts) { + Curl_llist_remove(&h->list, &sts->node, NULL); + hsts_free(sts); + } + return CURLE_OK; + } + + if(CURL_OFF_T_MAX - now < expires) + /* would overflow, use maximum value */ + expires = CURL_OFF_T_MAX; + else + expires += now; + + /* check if it already exists */ + sts = Curl_hsts(h, hostname, FALSE); + if(sts) { + /* just update these fields */ + sts->expires = expires; + sts->includeSubDomains = subdomains; + } + else + return hsts_create(h, hostname, subdomains, expires); + + return CURLE_OK; +} + +/* + * Return TRUE if the given host name is currently an HSTS one. + * + * The 'subdomain' argument tells the function if subdomain matching should be + * attempted. + */ +struct stsentry *Curl_hsts(struct hsts *h, const char *hostname, + bool subdomain) +{ + if(h) { + char buffer[MAX_HSTS_HOSTLEN + 1]; + time_t now = time(NULL); + size_t hlen = strlen(hostname); + struct Curl_llist_element *e; + struct Curl_llist_element *n; + + if((hlen > MAX_HSTS_HOSTLEN) || !hlen) + return NULL; + memcpy(buffer, hostname, hlen); + if(hostname[hlen-1] == '.') + /* remove the trailing dot */ + --hlen; + buffer[hlen] = 0; + hostname = buffer; + + for(e = h->list.head; e; e = n) { + struct stsentry *sts = e->ptr; + n = e->next; + if(sts->expires <= now) { + /* remove expired entries */ + Curl_llist_remove(&h->list, &sts->node, NULL); + hsts_free(sts); + continue; + } + if(subdomain && sts->includeSubDomains) { + size_t ntail = strlen(sts->host); + if(ntail < hlen) { + size_t offs = hlen - ntail; + if((hostname[offs-1] == '.') && + strncasecompare(&hostname[offs], sts->host, ntail)) + return sts; + } + } + if(strcasecompare(hostname, sts->host)) + return sts; + } + } + return NULL; /* no match */ +} + +/* + * Send this HSTS entry to the write callback. + */ +static CURLcode hsts_push(struct Curl_easy *data, + struct curl_index *i, + struct stsentry *sts, + bool *stop) +{ + struct curl_hstsentry e; + CURLSTScode sc; + struct tm stamp; + CURLcode result; + + e.name = (char *)sts->host; + e.namelen = strlen(sts->host); + e.includeSubDomains = sts->includeSubDomains; + + if(sts->expires != TIME_T_MAX) { + result = Curl_gmtime((time_t)sts->expires, &stamp); + if(result) + return result; + + msnprintf(e.expire, sizeof(e.expire), "%d%02d%02d %02d:%02d:%02d", + stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday, + stamp.tm_hour, stamp.tm_min, stamp.tm_sec); + } + else + strcpy(e.expire, UNLIMITED); + + sc = data->set.hsts_write(data, &e, i, + data->set.hsts_write_userp); + *stop = (sc != CURLSTS_OK); + return sc == CURLSTS_FAIL ? CURLE_BAD_FUNCTION_ARGUMENT : CURLE_OK; +} + +/* + * Write this single hsts entry to a single output line + */ +static CURLcode hsts_out(struct stsentry *sts, FILE *fp) +{ + struct tm stamp; + if(sts->expires != TIME_T_MAX) { + CURLcode result = Curl_gmtime((time_t)sts->expires, &stamp); + if(result) + return result; + fprintf(fp, "%s%s \"%d%02d%02d %02d:%02d:%02d\"\n", + sts->includeSubDomains ? ".": "", sts->host, + stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday, + stamp.tm_hour, stamp.tm_min, stamp.tm_sec); + } + else + fprintf(fp, "%s%s \"%s\"\n", + sts->includeSubDomains ? ".": "", sts->host, UNLIMITED); + return CURLE_OK; +} + + +/* + * Curl_https_save() writes the HSTS cache to file and callback. + */ +CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, + const char *file) +{ + struct Curl_llist_element *e; + struct Curl_llist_element *n; + CURLcode result = CURLE_OK; + FILE *out; + char *tempstore = NULL; + + if(!h) + /* no cache activated */ + return CURLE_OK; + + /* if no new name is given, use the one we stored from the load */ + if(!file && h->filename) + file = h->filename; + + if((h->flags & CURLHSTS_READONLYFILE) || !file || !file[0]) + /* marked as read-only, no file or zero length file name */ + goto skipsave; + + result = Curl_fopen(data, file, &out, &tempstore); + if(!result) { + fputs("# Your HSTS cache. https://curl.se/docs/hsts.html\n" + "# This file was generated by libcurl! Edit at your own risk.\n", + out); + for(e = h->list.head; e; e = n) { + struct stsentry *sts = e->ptr; + n = e->next; + result = hsts_out(sts, out); + if(result) + break; + } + fclose(out); + if(!result && tempstore && Curl_rename(tempstore, file)) + result = CURLE_WRITE_ERROR; + + if(result && tempstore) + unlink(tempstore); + } + free(tempstore); +skipsave: + if(data->set.hsts_write) { + /* if there's a write callback */ + struct curl_index i; /* count */ + i.total = h->list.size; + i.index = 0; + for(e = h->list.head; e; e = n) { + struct stsentry *sts = e->ptr; + bool stop; + n = e->next; + result = hsts_push(data, &i, sts, &stop); + if(result || stop) + break; + i.index++; + } + } + return result; +} + +/* only returns SERIOUS errors */ +static CURLcode hsts_add(struct hsts *h, char *line) +{ + /* Example lines: + example.com "20191231 10:00:00" + .example.net "20191231 10:00:00" + */ + char host[MAX_HSTS_HOSTLEN + 1]; + char date[MAX_HSTS_DATELEN + 1]; + int rc; + + rc = sscanf(line, + "%" MAX_HSTS_HOSTLENSTR "s \"%" MAX_HSTS_DATELENSTR "[^\"]\"", + host, date); + if(2 == rc) { + time_t expires = strcmp(date, UNLIMITED) ? Curl_getdate_capped(date) : + TIME_T_MAX; + CURLcode result = CURLE_OK; + char *p = host; + bool subdomain = FALSE; + struct stsentry *e; + if(p[0] == '.') { + p++; + subdomain = TRUE; + } + /* only add it if not already present */ + e = Curl_hsts(h, p, subdomain); + if(!e) + result = hsts_create(h, p, subdomain, expires); + else { + /* the same host name, use the largest expire time */ + if(expires > e->expires) + e->expires = expires; + } + if(result) + return result; + } + + return CURLE_OK; +} + +/* + * Load HSTS data from callback. + * + */ +static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h) +{ + /* if the HSTS read callback is set, use it */ + if(data->set.hsts_read) { + CURLSTScode sc; + DEBUGASSERT(h); + do { + char buffer[MAX_HSTS_HOSTLEN + 1]; + struct curl_hstsentry e; + e.name = buffer; + e.namelen = sizeof(buffer)-1; + e.includeSubDomains = FALSE; /* default */ + e.expire[0] = 0; + e.name[0] = 0; /* just to make it clean */ + sc = data->set.hsts_read(data, &e, data->set.hsts_read_userp); + if(sc == CURLSTS_OK) { + time_t expires; + CURLcode result; + if(!e.name[0]) + /* bail out if no name was stored */ + return CURLE_BAD_FUNCTION_ARGUMENT; + if(e.expire[0]) + expires = Curl_getdate_capped(e.expire); + else + expires = TIME_T_MAX; /* the end of time */ + result = hsts_create(h, e.name, + /* bitfield to bool conversion: */ + e.includeSubDomains ? TRUE : FALSE, + expires); + if(result) + return result; + } + else if(sc == CURLSTS_FAIL) + return CURLE_ABORTED_BY_CALLBACK; + } while(sc == CURLSTS_OK); + } + return CURLE_OK; +} + +/* + * Load the HSTS cache from the given file. The text based line-oriented file + * format is documented here: https://curl.se/docs/hsts.html + * + * This function only returns error on major problems that prevent hsts + * handling to work completely. It will ignore individual syntactical errors + * etc. + */ +static CURLcode hsts_load(struct hsts *h, const char *file) +{ + CURLcode result = CURLE_OK; + char *line = NULL; + FILE *fp; + + /* we need a private copy of the file name so that the hsts cache file + name survives an easy handle reset */ + free(h->filename); + h->filename = strdup(file); + if(!h->filename) + return CURLE_OUT_OF_MEMORY; + + fp = fopen(file, FOPEN_READTEXT); + if(fp) { + line = malloc(MAX_HSTS_LINE); + if(!line) + goto fail; + while(Curl_get_line(line, MAX_HSTS_LINE, fp)) { + char *lineptr = line; + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; + if(*lineptr == '#') + /* skip commented lines */ + continue; + + hsts_add(h, lineptr); + } + free(line); /* free the line buffer */ + fclose(fp); + } + return result; + +fail: + Curl_safefree(h->filename); + fclose(fp); + return CURLE_OUT_OF_MEMORY; +} + +/* + * Curl_hsts_loadfile() loads HSTS from file + */ +CURLcode Curl_hsts_loadfile(struct Curl_easy *data, + struct hsts *h, const char *file) +{ + DEBUGASSERT(h); + (void)data; + return hsts_load(h, file); +} + +/* + * Curl_hsts_loadcb() loads HSTS from callback + */ +CURLcode Curl_hsts_loadcb(struct Curl_easy *data, struct hsts *h) +{ + if(h) + return hsts_pull(data, h); + return CURLE_OK; +} + +void Curl_hsts_loadfiles(struct Curl_easy *data) +{ + struct curl_slist *l = data->state.hstslist; + if(l) { + Curl_share_lock(data, CURL_LOCK_DATA_HSTS, CURL_LOCK_ACCESS_SINGLE); + + while(l) { + (void)Curl_hsts_loadfile(data, data->hsts, l->data); + l = l->next; + } + Curl_share_unlock(data, CURL_LOCK_DATA_HSTS); + } +} + +#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */ diff --git a/Utilities/cmcurl/lib/hsts.h b/Utilities/cmcurl/lib/hsts.h new file mode 100644 index 0000000..d3431a5 --- /dev/null +++ b/Utilities/cmcurl/lib/hsts.h @@ -0,0 +1,69 @@ +#ifndef HEADER_CURL_HSTS_H +#define HEADER_CURL_HSTS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HSTS) +#include <curl/curl.h> +#include "llist.h" + +#ifdef DEBUGBUILD +extern time_t deltatime; +#endif + +struct stsentry { + struct Curl_llist_element node; + const char *host; + bool includeSubDomains; + curl_off_t expires; /* the timestamp of this entry's expiry */ +}; + +/* The HSTS cache. Needs to be able to tailmatch host names. */ +struct hsts { + struct Curl_llist list; + char *filename; + unsigned int flags; +}; + +struct hsts *Curl_hsts_init(void); +void Curl_hsts_cleanup(struct hsts **hp); +CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, + const char *sts); +struct stsentry *Curl_hsts(struct hsts *h, const char *hostname, + bool subdomain); +CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, + const char *file); +CURLcode Curl_hsts_loadfile(struct Curl_easy *data, + struct hsts *h, const char *file); +CURLcode Curl_hsts_loadcb(struct Curl_easy *data, + struct hsts *h); +void Curl_hsts_loadfiles(struct Curl_easy *data); +#else +#define Curl_hsts_cleanup(x) +#define Curl_hsts_loadcb(x,y) CURLE_OK +#define Curl_hsts_save(x,y,z) +#define Curl_hsts_loadfiles(x) +#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */ +#endif /* HEADER_CURL_HSTS_H */ diff --git a/Utilities/cmcurl/lib/http.c b/Utilities/cmcurl/lib/http.c new file mode 100644 index 0000000..be6d442 --- /dev/null +++ b/Utilities/cmcurl/lib/http.c @@ -0,0 +1,4954 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_HTTP + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#ifdef USE_HYPER +#include <hyper.h> +#endif + +#include "urldata.h" +#include <curl/curl.h> +#include "transfer.h" +#include "sendf.h" +#include "formdata.h" +#include "mime.h" +#include "progress.h" +#include "curl_base64.h" +#include "cookie.h" +#include "vauth/vauth.h" +#include "vtls/vtls.h" +#include "vquic/vquic.h" +#include "http_digest.h" +#include "http_ntlm.h" +#include "curl_ntlm_wb.h" +#include "http_negotiate.h" +#include "http_aws_sigv4.h" +#include "url.h" +#include "share.h" +#include "hostip.h" +#include "dynhds.h" +#include "http.h" +#include "select.h" +#include "parsedate.h" /* for the week day and month names */ +#include "strtoofft.h" +#include "multiif.h" +#include "strcase.h" +#include "content_encoding.h" +#include "http_proxy.h" +#include "warnless.h" +#include "http2.h" +#include "cfilters.h" +#include "connect.h" +#include "strdup.h" +#include "altsvc.h" +#include "hsts.h" +#include "ws.h" +#include "c-hyper.h" +#include "curl_ctype.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Forward declarations. + */ + +static int http_getsock_do(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks); +static bool http_should_fail(struct Curl_easy *data); + +static CURLcode http_setup_conn(struct Curl_easy *data, + struct connectdata *conn); +#ifdef USE_WEBSOCKETS +static CURLcode ws_setup_conn(struct Curl_easy *data, + struct connectdata *conn); +#endif + +/* + * HTTP handler interface. + */ +const struct Curl_handler Curl_handler_http = { + "HTTP", /* scheme */ + http_setup_conn, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_HTTP, /* defport */ + CURLPROTO_HTTP, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL +}; + +#ifdef USE_WEBSOCKETS +const struct Curl_handler Curl_handler_ws = { + "WS", /* scheme */ + ws_setup_conn, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + Curl_ws_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_HTTP, /* defport */ + CURLPROTO_WS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL +}; +#endif + +#ifdef USE_SSL +/* + * HTTPS handler interface. + */ +const struct Curl_handler Curl_handler_https = { + "HTTPS", /* scheme */ + http_setup_conn, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + NULL, /* connecting */ + ZERO_NULL, /* doing */ + NULL, /* proto_getsock */ + http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_HTTPS, /* defport */ + CURLPROTO_HTTPS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | PROTOPT_ALPN | /* flags */ + PROTOPT_USERPWDCTRL +}; + +#ifdef USE_WEBSOCKETS +const struct Curl_handler Curl_handler_wss = { + "WSS", /* scheme */ + ws_setup_conn, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + NULL, /* connecting */ + ZERO_NULL, /* doing */ + NULL, /* proto_getsock */ + http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + Curl_ws_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_HTTPS, /* defport */ + CURLPROTO_WSS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL +}; +#endif + +#endif + +static CURLcode http_setup_conn(struct Curl_easy *data, + struct connectdata *conn) +{ + /* allocate the HTTP-specific struct for the Curl_easy, only to survive + during this request */ + struct HTTP *http; + DEBUGASSERT(data->req.p.http == NULL); + + http = calloc(1, sizeof(struct HTTP)); + if(!http) + return CURLE_OUT_OF_MEMORY; + + data->req.p.http = http; + connkeep(conn, "HTTP default"); + + if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) { + CURLcode result = Curl_conn_may_http3(data, conn); + if(result) + return result; + } + + return CURLE_OK; +} + +#ifdef USE_WEBSOCKETS +static CURLcode ws_setup_conn(struct Curl_easy *data, + struct connectdata *conn) +{ + /* websockets is 1.1 only (for now) */ + data->state.httpwant = CURL_HTTP_VERSION_1_1; + return http_setup_conn(data, conn); +} +#endif + +#ifndef CURL_DISABLE_PROXY +/* + * checkProxyHeaders() checks the linked list of custom proxy headers + * if proxy headers are not available, then it will lookup into http header + * link list + * + * It takes a connectdata struct as input to see if this is a proxy request or + * not, as it then might check a different header list. Provide the header + * prefix without colon! + */ +char *Curl_checkProxyheaders(struct Curl_easy *data, + const struct connectdata *conn, + const char *thisheader, + const size_t thislen) +{ + struct curl_slist *head; + + for(head = (conn->bits.proxy && data->set.sep_headers) ? + data->set.proxyheaders : data->set.headers; + head; head = head->next) { + if(strncasecompare(head->data, thisheader, thislen) && + Curl_headersep(head->data[thislen])) + return head->data; + } + + return NULL; +} +#else +/* disabled */ +#define Curl_checkProxyheaders(x,y,z,a) NULL +#endif + +/* + * Strip off leading and trailing whitespace from the value in the + * given HTTP header line and return a strdupped copy. Returns NULL in + * case of allocation failure. Returns an empty string if the header value + * consists entirely of whitespace. + */ +char *Curl_copy_header_value(const char *header) +{ + const char *start; + const char *end; + char *value; + size_t len; + + /* Find the end of the header name */ + while(*header && (*header != ':')) + ++header; + + if(*header) + /* Skip over colon */ + ++header; + + /* Find the first non-space letter */ + start = header; + while(*start && ISSPACE(*start)) + start++; + + /* data is in the host encoding so + use '\r' and '\n' instead of 0x0d and 0x0a */ + end = strchr(start, '\r'); + if(!end) + end = strchr(start, '\n'); + if(!end) + end = strchr(start, '\0'); + if(!end) + return NULL; + + /* skip all trailing space letters */ + while((end > start) && ISSPACE(*end)) + end--; + + /* get length of the type */ + len = end - start + 1; + + value = malloc(len + 1); + if(!value) + return NULL; + + memcpy(value, start, len); + value[len] = 0; /* null-terminate */ + + return value; +} + +#ifndef CURL_DISABLE_HTTP_AUTH + +#ifndef CURL_DISABLE_BASIC_AUTH +/* + * http_output_basic() sets up an Authorization: header (or the proxy version) + * for HTTP Basic authentication. + * + * Returns CURLcode. + */ +static CURLcode http_output_basic(struct Curl_easy *data, bool proxy) +{ + size_t size = 0; + char *authorization = NULL; + char **userp; + const char *user; + const char *pwd; + CURLcode result; + char *out; + + /* credentials are unique per transfer for HTTP, do not use the ones for the + connection */ + if(proxy) { +#ifndef CURL_DISABLE_PROXY + userp = &data->state.aptr.proxyuserpwd; + user = data->state.aptr.proxyuser; + pwd = data->state.aptr.proxypasswd; +#else + return CURLE_NOT_BUILT_IN; +#endif + } + else { + userp = &data->state.aptr.userpwd; + user = data->state.aptr.user; + pwd = data->state.aptr.passwd; + } + + out = aprintf("%s:%s", user ? user : "", pwd ? pwd : ""); + if(!out) + return CURLE_OUT_OF_MEMORY; + + result = Curl_base64_encode(out, strlen(out), &authorization, &size); + if(result) + goto fail; + + if(!authorization) { + result = CURLE_REMOTE_ACCESS_DENIED; + goto fail; + } + + free(*userp); + *userp = aprintf("%sAuthorization: Basic %s\r\n", + proxy ? "Proxy-" : "", + authorization); + free(authorization); + if(!*userp) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + +fail: + free(out); + return result; +} + +#endif + +#ifndef CURL_DISABLE_BEARER_AUTH +/* + * http_output_bearer() sets up an Authorization: header + * for HTTP Bearer authentication. + * + * Returns CURLcode. + */ +static CURLcode http_output_bearer(struct Curl_easy *data) +{ + char **userp; + CURLcode result = CURLE_OK; + + userp = &data->state.aptr.userpwd; + free(*userp); + *userp = aprintf("Authorization: Bearer %s\r\n", + data->set.str[STRING_BEARER]); + + if(!*userp) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + +fail: + return result; +} + +#endif + +#endif + +/* pickoneauth() selects the most favourable authentication method from the + * ones available and the ones we want. + * + * return TRUE if one was picked + */ +static bool pickoneauth(struct auth *pick, unsigned long mask) +{ + bool picked; + /* only deal with authentication we want */ + unsigned long avail = pick->avail & pick->want & mask; + picked = TRUE; + + /* The order of these checks is highly relevant, as this will be the order + of preference in case of the existence of multiple accepted types. */ + if(avail & CURLAUTH_NEGOTIATE) + pick->picked = CURLAUTH_NEGOTIATE; +#ifndef CURL_DISABLE_BEARER_AUTH + else if(avail & CURLAUTH_BEARER) + pick->picked = CURLAUTH_BEARER; +#endif +#ifndef CURL_DISABLE_DIGEST_AUTH + else if(avail & CURLAUTH_DIGEST) + pick->picked = CURLAUTH_DIGEST; +#endif + else if(avail & CURLAUTH_NTLM) + pick->picked = CURLAUTH_NTLM; + else if(avail & CURLAUTH_NTLM_WB) + pick->picked = CURLAUTH_NTLM_WB; +#ifndef CURL_DISABLE_BASIC_AUTH + else if(avail & CURLAUTH_BASIC) + pick->picked = CURLAUTH_BASIC; +#endif +#ifndef CURL_DISABLE_AWS + else if(avail & CURLAUTH_AWS_SIGV4) + pick->picked = CURLAUTH_AWS_SIGV4; +#endif + else { + pick->picked = CURLAUTH_PICKNONE; /* we select to use nothing */ + picked = FALSE; + } + pick->avail = CURLAUTH_NONE; /* clear it here */ + + return picked; +} + +/* + * http_perhapsrewind() + * + * If we are doing POST or PUT { + * If we have more data to send { + * If we are doing NTLM { + * Keep sending since we must not disconnect + * } + * else { + * If there is more than just a little data left to send, close + * the current connection by force. + * } + * } + * If we have sent any data { + * If we don't have track of all the data { + * call app to tell it to rewind + * } + * else { + * rewind internally so that the operation can restart fine + * } + * } + * } + */ +static CURLcode http_perhapsrewind(struct Curl_easy *data, + struct connectdata *conn) +{ + struct HTTP *http = data->req.p.http; + curl_off_t bytessent; + curl_off_t expectsend = -1; /* default is unknown */ + + if(!http) + /* If this is still NULL, we have not reach very far and we can safely + skip this rewinding stuff */ + return CURLE_OK; + + switch(data->state.httpreq) { + case HTTPREQ_GET: + case HTTPREQ_HEAD: + return CURLE_OK; + default: + break; + } + + bytessent = data->req.writebytecount; + + if(conn->bits.authneg) { + /* This is a state where we are known to be negotiating and we don't send + any data then. */ + expectsend = 0; + } + else if(!conn->bits.protoconnstart) { + /* HTTP CONNECT in progress: there is no body */ + expectsend = 0; + } + else { + /* figure out how much data we are expected to send */ + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_PUT: + if(data->state.infilesize != -1) + expectsend = data->state.infilesize; + break; + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + expectsend = http->postsize; + break; + default: + break; + } + } + + data->state.rewindbeforesend = FALSE; /* default */ + + if((expectsend == -1) || (expectsend > bytessent)) { +#if defined(USE_NTLM) + /* There is still data left to send */ + if((data->state.authproxy.picked == CURLAUTH_NTLM) || + (data->state.authhost.picked == CURLAUTH_NTLM) || + (data->state.authproxy.picked == CURLAUTH_NTLM_WB) || + (data->state.authhost.picked == CURLAUTH_NTLM_WB)) { + if(((expectsend - bytessent) < 2000) || + (conn->http_ntlm_state != NTLMSTATE_NONE) || + (conn->proxy_ntlm_state != NTLMSTATE_NONE)) { + /* The NTLM-negotiation has started *OR* there is just a little (<2K) + data left to send, keep on sending. */ + + /* rewind data when completely done sending! */ + if(!conn->bits.authneg && (conn->writesockfd != CURL_SOCKET_BAD)) { + data->state.rewindbeforesend = TRUE; + infof(data, "Rewind stream before next send"); + } + + return CURLE_OK; + } + + if(conn->bits.close) + /* this is already marked to get closed */ + return CURLE_OK; + + infof(data, "NTLM send, close instead of sending %" + CURL_FORMAT_CURL_OFF_T " bytes", + (curl_off_t)(expectsend - bytessent)); + } +#endif +#if defined(USE_SPNEGO) + /* There is still data left to send */ + if((data->state.authproxy.picked == CURLAUTH_NEGOTIATE) || + (data->state.authhost.picked == CURLAUTH_NEGOTIATE)) { + if(((expectsend - bytessent) < 2000) || + (conn->http_negotiate_state != GSS_AUTHNONE) || + (conn->proxy_negotiate_state != GSS_AUTHNONE)) { + /* The NEGOTIATE-negotiation has started *OR* + there is just a little (<2K) data left to send, keep on sending. */ + + /* rewind data when completely done sending! */ + if(!conn->bits.authneg && (conn->writesockfd != CURL_SOCKET_BAD)) { + data->state.rewindbeforesend = TRUE; + infof(data, "Rewind stream before next send"); + } + + return CURLE_OK; + } + + if(conn->bits.close) + /* this is already marked to get closed */ + return CURLE_OK; + + infof(data, "NEGOTIATE send, close instead of sending %" + CURL_FORMAT_CURL_OFF_T " bytes", + (curl_off_t)(expectsend - bytessent)); + } +#endif + + /* This is not NEGOTIATE/NTLM or many bytes left to send: close */ + streamclose(conn, "Mid-auth HTTP and much data left to send"); + data->req.size = 0; /* don't download any more than 0 bytes */ + + /* There still is data left to send, but this connection is marked for + closure so we can safely do the rewind right now */ + } + + if(bytessent) { + /* mark for rewind since if we already sent something */ + data->state.rewindbeforesend = TRUE; + infof(data, "Please rewind output before next send"); + } + + return CURLE_OK; +} + +/* + * Curl_http_auth_act() gets called when all HTTP headers have been received + * and it checks what authentication methods that are available and decides + * which one (if any) to use. It will set 'newurl' if an auth method was + * picked. + */ + +CURLcode Curl_http_auth_act(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + bool pickhost = FALSE; + bool pickproxy = FALSE; + CURLcode result = CURLE_OK; + unsigned long authmask = ~0ul; + + if(!data->set.str[STRING_BEARER]) + authmask &= (unsigned long)~CURLAUTH_BEARER; + + if(100 <= data->req.httpcode && data->req.httpcode <= 199) + /* this is a transient response code, ignore */ + return CURLE_OK; + + if(data->state.authproblem) + return data->set.http_fail_on_error?CURLE_HTTP_RETURNED_ERROR:CURLE_OK; + + if((data->state.aptr.user || data->set.str[STRING_BEARER]) && + ((data->req.httpcode == 401) || + (conn->bits.authneg && data->req.httpcode < 300))) { + pickhost = pickoneauth(&data->state.authhost, authmask); + if(!pickhost) + data->state.authproblem = TRUE; + if(data->state.authhost.picked == CURLAUTH_NTLM && + conn->httpversion > 11) { + infof(data, "Forcing HTTP/1.1 for NTLM"); + connclose(conn, "Force HTTP/1.1 connection"); + data->state.httpwant = CURL_HTTP_VERSION_1_1; + } + } +#ifndef CURL_DISABLE_PROXY + if(conn->bits.proxy_user_passwd && + ((data->req.httpcode == 407) || + (conn->bits.authneg && data->req.httpcode < 300))) { + pickproxy = pickoneauth(&data->state.authproxy, + authmask & ~CURLAUTH_BEARER); + if(!pickproxy) + data->state.authproblem = TRUE; + } +#endif + + if(pickhost || pickproxy) { + if((data->state.httpreq != HTTPREQ_GET) && + (data->state.httpreq != HTTPREQ_HEAD) && + !data->state.rewindbeforesend) { + result = http_perhapsrewind(data, conn); + if(result) + return result; + } + /* In case this is GSS auth, the newurl field is already allocated so + we must make sure to free it before allocating a new one. As figured + out in bug #2284386 */ + Curl_safefree(data->req.newurl); + data->req.newurl = strdup(data->state.url); /* clone URL */ + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + } + else if((data->req.httpcode < 300) && + (!data->state.authhost.done) && + conn->bits.authneg) { + /* no (known) authentication available, + authentication is not "done" yet and + no authentication seems to be required and + we didn't try HEAD or GET */ + if((data->state.httpreq != HTTPREQ_GET) && + (data->state.httpreq != HTTPREQ_HEAD)) { + data->req.newurl = strdup(data->state.url); /* clone URL */ + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + data->state.authhost.done = TRUE; + } + } + if(http_should_fail(data)) { + failf(data, "The requested URL returned error: %d", + data->req.httpcode); + result = CURLE_HTTP_RETURNED_ERROR; + } + + return result; +} + +#ifndef CURL_DISABLE_HTTP_AUTH +/* + * Output the correct authentication header depending on the auth type + * and whether or not it is to a proxy. + */ +static CURLcode +output_auth_headers(struct Curl_easy *data, + struct connectdata *conn, + struct auth *authstatus, + const char *request, + const char *path, + bool proxy) +{ + const char *auth = NULL; + CURLcode result = CURLE_OK; + (void)conn; + +#ifdef CURL_DISABLE_DIGEST_AUTH + (void)request; + (void)path; +#endif +#ifndef CURL_DISABLE_AWS + if(authstatus->picked == CURLAUTH_AWS_SIGV4) { + auth = "AWS_SIGV4"; + result = Curl_output_aws_sigv4(data, proxy); + if(result) + return result; + } + else +#endif +#ifdef USE_SPNEGO + if(authstatus->picked == CURLAUTH_NEGOTIATE) { + auth = "Negotiate"; + result = Curl_output_negotiate(data, conn, proxy); + if(result) + return result; + } + else +#endif +#ifdef USE_NTLM + if(authstatus->picked == CURLAUTH_NTLM) { + auth = "NTLM"; + result = Curl_output_ntlm(data, proxy); + if(result) + return result; + } + else +#endif +#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) + if(authstatus->picked == CURLAUTH_NTLM_WB) { + auth = "NTLM_WB"; + result = Curl_output_ntlm_wb(data, conn, proxy); + if(result) + return result; + } + else +#endif +#ifndef CURL_DISABLE_DIGEST_AUTH + if(authstatus->picked == CURLAUTH_DIGEST) { + auth = "Digest"; + result = Curl_output_digest(data, + proxy, + (const unsigned char *)request, + (const unsigned char *)path); + if(result) + return result; + } + else +#endif +#ifndef CURL_DISABLE_BASIC_AUTH + if(authstatus->picked == CURLAUTH_BASIC) { + /* Basic */ + if( +#ifndef CURL_DISABLE_PROXY + (proxy && conn->bits.proxy_user_passwd && + !Curl_checkProxyheaders(data, conn, STRCONST("Proxy-authorization"))) || +#endif + (!proxy && data->state.aptr.user && + !Curl_checkheaders(data, STRCONST("Authorization")))) { + auth = "Basic"; + result = http_output_basic(data, proxy); + if(result) + return result; + } + + /* NOTE: this function should set 'done' TRUE, as the other auth + functions work that way */ + authstatus->done = TRUE; + } +#endif +#ifndef CURL_DISABLE_BEARER_AUTH + if(authstatus->picked == CURLAUTH_BEARER) { + /* Bearer */ + if((!proxy && data->set.str[STRING_BEARER] && + !Curl_checkheaders(data, STRCONST("Authorization")))) { + auth = "Bearer"; + result = http_output_bearer(data); + if(result) + return result; + } + + /* NOTE: this function should set 'done' TRUE, as the other auth + functions work that way */ + authstatus->done = TRUE; + } +#endif + + if(auth) { +#ifndef CURL_DISABLE_PROXY + infof(data, "%s auth using %s with user '%s'", + proxy ? "Proxy" : "Server", auth, + proxy ? (data->state.aptr.proxyuser ? + data->state.aptr.proxyuser : "") : + (data->state.aptr.user ? + data->state.aptr.user : "")); +#else + (void)proxy; + infof(data, "Server auth using %s with user '%s'", + auth, data->state.aptr.user ? + data->state.aptr.user : ""); +#endif + authstatus->multipass = (!authstatus->done) ? TRUE : FALSE; + } + else + authstatus->multipass = FALSE; + + return result; +} + +/** + * Curl_http_output_auth() setups the authentication headers for the + * host/proxy and the correct authentication + * method. data->state.authdone is set to TRUE when authentication is + * done. + * + * @param conn all information about the current connection + * @param request pointer to the request keyword + * @param path pointer to the requested path; should include query part + * @param proxytunnel boolean if this is the request setting up a "proxy + * tunnel" + * + * @returns CURLcode + */ +CURLcode +Curl_http_output_auth(struct Curl_easy *data, + struct connectdata *conn, + const char *request, + Curl_HttpReq httpreq, + const char *path, + bool proxytunnel) /* TRUE if this is the request setting + up the proxy tunnel */ +{ + CURLcode result = CURLE_OK; + struct auth *authhost; + struct auth *authproxy; + + DEBUGASSERT(data); + + authhost = &data->state.authhost; + authproxy = &data->state.authproxy; + + if( +#ifndef CURL_DISABLE_PROXY + (conn->bits.httpproxy && conn->bits.proxy_user_passwd) || +#endif + data->state.aptr.user || +#ifdef USE_SPNEGO + authhost->want & CURLAUTH_NEGOTIATE || + authproxy->want & CURLAUTH_NEGOTIATE || +#endif + data->set.str[STRING_BEARER]) + /* continue please */; + else { + authhost->done = TRUE; + authproxy->done = TRUE; + return CURLE_OK; /* no authentication with no user or password */ + } + + if(authhost->want && !authhost->picked) + /* The app has selected one or more methods, but none has been picked + so far by a server round-trip. Then we set the picked one to the + want one, and if this is one single bit it'll be used instantly. */ + authhost->picked = authhost->want; + + if(authproxy->want && !authproxy->picked) + /* The app has selected one or more methods, but none has been picked so + far by a proxy round-trip. Then we set the picked one to the want one, + and if this is one single bit it'll be used instantly. */ + authproxy->picked = authproxy->want; + +#ifndef CURL_DISABLE_PROXY + /* Send proxy authentication header if needed */ + if(conn->bits.httpproxy && + (conn->bits.tunnel_proxy == (bit)proxytunnel)) { + result = output_auth_headers(data, conn, authproxy, request, path, TRUE); + if(result) + return result; + } + else +#else + (void)proxytunnel; +#endif /* CURL_DISABLE_PROXY */ + /* we have no proxy so let's pretend we're done authenticating + with it */ + authproxy->done = TRUE; + + /* To prevent the user+password to get sent to other than the original host + due to a location-follow */ + if(Curl_auth_allowed_to_host(data) +#ifndef CURL_DISABLE_NETRC + || conn->bits.netrc +#endif + ) + result = output_auth_headers(data, conn, authhost, request, path, FALSE); + else + authhost->done = TRUE; + + if(((authhost->multipass && !authhost->done) || + (authproxy->multipass && !authproxy->done)) && + (httpreq != HTTPREQ_GET) && + (httpreq != HTTPREQ_HEAD)) { + /* Auth is required and we are not authenticated yet. Make a PUT or POST + with content-length zero as a "probe". */ + conn->bits.authneg = TRUE; + } + else + conn->bits.authneg = FALSE; + + return result; +} + +#else +/* when disabled */ +CURLcode +Curl_http_output_auth(struct Curl_easy *data, + struct connectdata *conn, + const char *request, + Curl_HttpReq httpreq, + const char *path, + bool proxytunnel) +{ + (void)data; + (void)conn; + (void)request; + (void)httpreq; + (void)path; + (void)proxytunnel; + return CURLE_OK; +} +#endif + +#if defined(USE_SPNEGO) || defined(USE_NTLM) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_BASIC_AUTH) || \ + !defined(CURL_DISABLE_BEARER_AUTH) +static int is_valid_auth_separator(char ch) +{ + return ch == '\0' || ch == ',' || ISSPACE(ch); +} +#endif + +/* + * Curl_http_input_auth() deals with Proxy-Authenticate: and WWW-Authenticate: + * headers. They are dealt with both in the transfer.c main loop and in the + * proxy CONNECT loop. + */ +CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, + const char *auth) /* the first non-space */ +{ + /* + * This resource requires authentication + */ + struct connectdata *conn = data->conn; +#ifdef USE_SPNEGO + curlnegotiate *negstate = proxy ? &conn->proxy_negotiate_state : + &conn->http_negotiate_state; +#endif +#if defined(USE_SPNEGO) || \ + defined(USE_NTLM) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_BASIC_AUTH) || \ + !defined(CURL_DISABLE_BEARER_AUTH) + + unsigned long *availp; + struct auth *authp; + + if(proxy) { + availp = &data->info.proxyauthavail; + authp = &data->state.authproxy; + } + else { + availp = &data->info.httpauthavail; + authp = &data->state.authhost; + } +#else + (void) proxy; +#endif + + (void) conn; /* In case conditionals make it unused. */ + + /* + * Here we check if we want the specific single authentication (using ==) and + * if we do, we initiate usage of it. + * + * If the provided authentication is wanted as one out of several accepted + * types (using &), we OR this authentication type to the authavail + * variable. + * + * Note: + * + * ->picked is first set to the 'want' value (one or more bits) before the + * request is sent, and then it is again set _after_ all response 401/407 + * headers have been received but then only to a single preferred method + * (bit). + */ + + while(*auth) { +#ifdef USE_SPNEGO + if(checkprefix("Negotiate", auth) && is_valid_auth_separator(auth[9])) { + if((authp->avail & CURLAUTH_NEGOTIATE) || + Curl_auth_is_spnego_supported()) { + *availp |= CURLAUTH_NEGOTIATE; + authp->avail |= CURLAUTH_NEGOTIATE; + + if(authp->picked == CURLAUTH_NEGOTIATE) { + CURLcode result = Curl_input_negotiate(data, conn, proxy, auth); + if(!result) { + free(data->req.newurl); + data->req.newurl = strdup(data->state.url); + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + data->state.authproblem = FALSE; + /* we received a GSS auth token and we dealt with it fine */ + *negstate = GSS_AUTHRECV; + } + else + data->state.authproblem = TRUE; + } + } + } + else +#endif +#ifdef USE_NTLM + /* NTLM support requires the SSL crypto libs */ + if(checkprefix("NTLM", auth) && is_valid_auth_separator(auth[4])) { + if((authp->avail & CURLAUTH_NTLM) || + (authp->avail & CURLAUTH_NTLM_WB) || + Curl_auth_is_ntlm_supported()) { + *availp |= CURLAUTH_NTLM; + authp->avail |= CURLAUTH_NTLM; + + if(authp->picked == CURLAUTH_NTLM || + authp->picked == CURLAUTH_NTLM_WB) { + /* NTLM authentication is picked and activated */ + CURLcode result = Curl_input_ntlm(data, proxy, auth); + if(!result) { + data->state.authproblem = FALSE; +#ifdef NTLM_WB_ENABLED + if(authp->picked == CURLAUTH_NTLM_WB) { + *availp &= ~CURLAUTH_NTLM; + authp->avail &= ~CURLAUTH_NTLM; + *availp |= CURLAUTH_NTLM_WB; + authp->avail |= CURLAUTH_NTLM_WB; + + result = Curl_input_ntlm_wb(data, conn, proxy, auth); + if(result) { + infof(data, "Authentication problem. Ignoring this."); + data->state.authproblem = TRUE; + } + } +#endif + } + else { + infof(data, "Authentication problem. Ignoring this."); + data->state.authproblem = TRUE; + } + } + } + } + else +#endif +#ifndef CURL_DISABLE_DIGEST_AUTH + if(checkprefix("Digest", auth) && is_valid_auth_separator(auth[6])) { + if((authp->avail & CURLAUTH_DIGEST) != 0) + infof(data, "Ignoring duplicate digest auth header."); + else if(Curl_auth_is_digest_supported()) { + CURLcode result; + + *availp |= CURLAUTH_DIGEST; + authp->avail |= CURLAUTH_DIGEST; + + /* We call this function on input Digest headers even if Digest + * authentication isn't activated yet, as we need to store the + * incoming data from this header in case we are going to use + * Digest */ + result = Curl_input_digest(data, proxy, auth); + if(result) { + infof(data, "Authentication problem. Ignoring this."); + data->state.authproblem = TRUE; + } + } + } + else +#endif +#ifndef CURL_DISABLE_BASIC_AUTH + if(checkprefix("Basic", auth) && + is_valid_auth_separator(auth[5])) { + *availp |= CURLAUTH_BASIC; + authp->avail |= CURLAUTH_BASIC; + if(authp->picked == CURLAUTH_BASIC) { + /* We asked for Basic authentication but got a 40X back + anyway, which basically means our name+password isn't + valid. */ + authp->avail = CURLAUTH_NONE; + infof(data, "Authentication problem. Ignoring this."); + data->state.authproblem = TRUE; + } + } + else +#endif +#ifndef CURL_DISABLE_BEARER_AUTH + if(checkprefix("Bearer", auth) && + is_valid_auth_separator(auth[6])) { + *availp |= CURLAUTH_BEARER; + authp->avail |= CURLAUTH_BEARER; + if(authp->picked == CURLAUTH_BEARER) { + /* We asked for Bearer authentication but got a 40X back + anyway, which basically means our token isn't valid. */ + authp->avail = CURLAUTH_NONE; + infof(data, "Authentication problem. Ignoring this."); + data->state.authproblem = TRUE; + } + } +#else + { + /* + * Empty block to terminate the if-else chain correctly. + * + * A semicolon would yield the same result here, but can cause a + * compiler warning when -Wextra is enabled. + */ + } +#endif + + /* there may be multiple methods on one line, so keep reading */ + while(*auth && *auth != ',') /* read up to the next comma */ + auth++; + if(*auth == ',') /* if we're on a comma, skip it */ + auth++; + while(*auth && ISSPACE(*auth)) + auth++; + } + + return CURLE_OK; +} + +/** + * http_should_fail() determines whether an HTTP response has gotten us + * into an error state or not. + * + * @retval FALSE communications should continue + * + * @retval TRUE communications should not continue + */ +static bool http_should_fail(struct Curl_easy *data) +{ + int httpcode; + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + + httpcode = data->req.httpcode; + + /* + ** If we haven't been asked to fail on error, + ** don't fail. + */ + if(!data->set.http_fail_on_error) + return FALSE; + + /* + ** Any code < 400 is never terminal. + */ + if(httpcode < 400) + return FALSE; + + /* + ** A 416 response to a resume request is presumably because the file is + ** already completely downloaded and thus not actually a fail. + */ + if(data->state.resume_from && data->state.httpreq == HTTPREQ_GET && + httpcode == 416) + return FALSE; + + /* + ** Any code >= 400 that's not 401 or 407 is always + ** a terminal error + */ + if((httpcode != 401) && (httpcode != 407)) + return TRUE; + + /* + ** All we have left to deal with is 401 and 407 + */ + DEBUGASSERT((httpcode == 401) || (httpcode == 407)); + + /* + ** 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. So, if we've been to asked to authenticate a + ** particular stage, and we've done it, we're OK. But, if + ** we're already completely authenticated, it's not OK to + ** get another 401 or 407. + ** + ** It is possible for authentication to go stale such that + ** the client needs to reauthenticate. Once that info is + ** available, use it here. + */ + + /* + ** Either we're not authenticating, or we're supposed to + ** be authenticating something else. This is an error. + */ + if((httpcode == 401) && !data->state.aptr.user) + return TRUE; +#ifndef CURL_DISABLE_PROXY + if((httpcode == 407) && !data->conn->bits.proxy_user_passwd) + return TRUE; +#endif + + return data->state.authproblem; +} + +/* + * readmoredata() is a "fread() emulation" to provide POST and/or request + * data. It is used when a huge POST is to be made and the entire chunk wasn't + * sent in the first send(). This function will then be called from the + * transfer.c loop when more data is to be sent to the peer. + * + * Returns the amount of bytes it filled the buffer with. + */ +static size_t readmoredata(char *buffer, + size_t size, + size_t nitems, + void *userp) +{ + struct HTTP *http = (struct HTTP *)userp; + struct Curl_easy *data = http->backup.data; + size_t fullsize = size * nitems; + + if(!http->postsize) + /* nothing to return */ + return 0; + + /* make sure that an HTTP request is never sent away chunked! */ + data->req.forbidchunk = (http->sending == HTTPSEND_REQUEST)?TRUE:FALSE; + + if(data->set.max_send_speed && + (data->set.max_send_speed < (curl_off_t)fullsize) && + (data->set.max_send_speed < http->postsize)) + /* speed limit */ + fullsize = (size_t)data->set.max_send_speed; + + else if(http->postsize <= (curl_off_t)fullsize) { + memcpy(buffer, http->postdata, (size_t)http->postsize); + fullsize = (size_t)http->postsize; + + if(http->backup.postsize) { + /* move backup data into focus and continue on that */ + http->postdata = http->backup.postdata; + http->postsize = http->backup.postsize; + data->state.fread_func = http->backup.fread_func; + data->state.in = http->backup.fread_in; + + http->sending++; /* move one step up */ + + http->backup.postsize = 0; + } + else + http->postsize = 0; + + return fullsize; + } + + memcpy(buffer, http->postdata, fullsize); + http->postdata += fullsize; + http->postsize -= fullsize; + + return fullsize; +} + +/* + * Curl_buffer_send() sends a header buffer and frees all associated + * memory. Body data may be appended to the header data if desired. + * + * Returns CURLcode + */ +CURLcode Curl_buffer_send(struct dynbuf *in, + struct Curl_easy *data, + struct HTTP *http, + /* add the number of sent bytes to this + counter */ + curl_off_t *bytes_written, + /* how much of the buffer contains body data */ + curl_off_t included_body_bytes, + int sockindex) +{ + ssize_t amount; + CURLcode result; + char *ptr; + size_t size; + struct connectdata *conn = data->conn; + size_t sendsize; + size_t headersize; + + DEBUGASSERT(sockindex <= SECONDARYSOCKET && sockindex >= 0); + + /* The looping below is required since we use non-blocking sockets, but due + to the circumstances we will just loop and try again and again etc */ + + ptr = Curl_dyn_ptr(in); + size = Curl_dyn_len(in); + + headersize = size - (size_t)included_body_bytes; /* the initial part that + isn't body is header */ + + DEBUGASSERT(size > (size_t)included_body_bytes); + + if((conn->handler->flags & PROTOPT_SSL +#ifndef CURL_DISABLE_PROXY + || IS_HTTPS_PROXY(conn->http_proxy.proxytype) +#endif + ) + && conn->httpversion < 20) { + /* Make sure this doesn't send more body bytes than what the max send + speed says. The request bytes do not count to the max speed. + */ + if(data->set.max_send_speed && + (included_body_bytes > data->set.max_send_speed)) { + curl_off_t overflow = included_body_bytes - data->set.max_send_speed; + DEBUGASSERT((size_t)overflow < size); + sendsize = size - (size_t)overflow; + } + else + sendsize = size; + + /* OpenSSL is very picky and we must send the SAME buffer pointer to the + library when we attempt to re-send this buffer. Sending the same data + is not enough, we must use the exact same address. For this reason, we + must copy the data to the uploadbuffer first, since that is the buffer + we will be using if this send is retried later. + */ + result = Curl_get_upload_buffer(data); + if(result) { + /* malloc failed, free memory and return to the caller */ + Curl_dyn_free(in); + return result; + } + /* We never send more than upload_buffer_size bytes in one single chunk + when we speak HTTPS, as if only a fraction of it is sent now, this data + needs to fit into the normal read-callback buffer later on and that + buffer is using this size. + */ + if(sendsize > (size_t)data->set.upload_buffer_size) + sendsize = (size_t)data->set.upload_buffer_size; + + memcpy(data->state.ulbuf, ptr, sendsize); + ptr = data->state.ulbuf; + } + else { +#ifdef CURLDEBUG + /* Allow debug builds to override this logic to force short initial + sends + */ + char *p = getenv("CURL_SMALLREQSEND"); + if(p) { + size_t altsize = (size_t)strtoul(p, NULL, 10); + if(altsize) + sendsize = CURLMIN(size, altsize); + else + sendsize = size; + } + else +#endif + { + /* Make sure this doesn't send more body bytes than what the max send + speed says. The request bytes do not count to the max speed. + */ + if(data->set.max_send_speed && + (included_body_bytes > data->set.max_send_speed)) { + curl_off_t overflow = included_body_bytes - data->set.max_send_speed; + DEBUGASSERT((size_t)overflow < size); + sendsize = size - (size_t)overflow; + } + else + sendsize = size; + } + + /* We currently cannot send more that this for http here: + * - if sending blocks, it return 0 as amount + * - we then whisk aside the `in` into the `http` struct + * and install our own `data->state.fread_func` that + * on subsequent calls reads `in` empty. + * - when the whisked away `in` is empty, the `fread_func` + * is restored to its original state. + * The problem is that `fread_func` can only return + * `upload_buffer_size` lengths. If the send we do here + * is larger and blocks, we do re-sending with smaller + * amounts of data and connection filters do not like + * that. + */ + if(http && (sendsize > (size_t)data->set.upload_buffer_size)) + sendsize = (size_t)data->set.upload_buffer_size; + } + + result = Curl_nwrite(data, sockindex, ptr, sendsize, &amount); + + if(!result) { + /* + * Note that we may not send the entire chunk at once, and we have a set + * number of data bytes at the end of the big buffer (out of which we may + * only send away a part). + */ + /* how much of the header that was sent */ + size_t headlen = (size_t)amount>headersize ? headersize : (size_t)amount; + size_t bodylen = amount - headlen; + + /* this data _may_ contain binary stuff */ + Curl_debug(data, CURLINFO_HEADER_OUT, ptr, headlen); + if(bodylen) + /* there was body data sent beyond the initial header part, pass that on + to the debug callback too */ + Curl_debug(data, CURLINFO_DATA_OUT, ptr + headlen, bodylen); + + /* 'amount' can never be a very large value here so typecasting it so a + signed 31 bit value should not cause problems even if ssize_t is + 64bit */ + *bytes_written += (long)amount; + + if(http) { + /* if we sent a piece of the body here, up the byte counter for it + accordingly */ + data->req.writebytecount += bodylen; + Curl_pgrsSetUploadCounter(data, data->req.writebytecount); + + if((size_t)amount != size) { + /* The whole request could not be sent in one system call. We must + queue it up and send it later when we get the chance. We must not + loop here and wait until it might work again. */ + + size -= amount; + + ptr = Curl_dyn_ptr(in) + amount; + + /* backup the currently set pointers */ + http->backup.fread_func = data->state.fread_func; + http->backup.fread_in = data->state.in; + http->backup.postdata = http->postdata; + http->backup.postsize = http->postsize; + http->backup.data = data; + + /* set the new pointers for the request-sending */ + data->state.fread_func = (curl_read_callback)readmoredata; + data->state.in = (void *)http; + http->postdata = ptr; + http->postsize = (curl_off_t)size; + + /* this much data is remaining header: */ + data->req.pendingheader = headersize - headlen; + + http->send_buffer = *in; /* copy the whole struct */ + http->sending = HTTPSEND_REQUEST; + return CURLE_OK; + } + http->sending = HTTPSEND_BODY; + /* the full buffer was sent, clean up and return */ + } + else { + if((size_t)amount != size) + /* We have no continue-send mechanism now, fail. This can only happen + when this function is used from the CONNECT sending function. We + currently (stupidly) assume that the whole request is always sent + away in the first single chunk. + + This needs FIXing. + */ + return CURLE_SEND_ERROR; + } + } + Curl_dyn_free(in); + + /* no remaining header data */ + data->req.pendingheader = 0; + return result; +} + +/* end of the add_buffer functions */ +/* ------------------------------------------------------------------------- */ + + + +/* + * Curl_compareheader() + * + * Returns TRUE if 'headerline' contains the 'header' with given 'content'. + * Pass headers WITH the colon. + */ +bool +Curl_compareheader(const char *headerline, /* line to check */ + const char *header, /* header keyword _with_ colon */ + const size_t hlen, /* len of the keyword in bytes */ + const char *content, /* content string to find */ + const size_t clen) /* len of the content in bytes */ +{ + /* RFC2616, section 4.2 says: "Each header field consists of a name followed + * by a colon (":") and the field value. Field names are case-insensitive. + * The field value MAY be preceded by any amount of LWS, though a single SP + * is preferred." */ + + size_t len; + const char *start; + const char *end; + DEBUGASSERT(hlen); + DEBUGASSERT(clen); + DEBUGASSERT(header); + DEBUGASSERT(content); + + if(!strncasecompare(headerline, header, hlen)) + return FALSE; /* doesn't start with header */ + + /* pass the header */ + start = &headerline[hlen]; + + /* pass all whitespace */ + while(*start && ISSPACE(*start)) + start++; + + /* find the end of the header line */ + end = strchr(start, '\r'); /* lines end with CRLF */ + if(!end) { + /* in case there's a non-standard compliant line here */ + end = strchr(start, '\n'); + + if(!end) + /* hm, there's no line ending here, use the zero byte! */ + end = strchr(start, '\0'); + } + + len = end-start; /* length of the content part of the input line */ + + /* find the content string in the rest of the line */ + for(; len >= clen; len--, start++) { + if(strncasecompare(start, content, clen)) + return TRUE; /* match! */ + } + + return FALSE; /* no match */ +} + +/* + * Curl_http_connect() performs HTTP stuff to do at connect-time, called from + * the generic Curl_connect(). + */ +CURLcode Curl_http_connect(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + + /* We default to persistent connections. We set this already in this connect + function to make the reuse checks properly be able to check this bit. */ + connkeep(conn, "HTTP default"); + + return Curl_conn_connect(data, FIRSTSOCKET, FALSE, done); +} + +/* this returns the socket to wait for in the DO and DOING state for the multi + interface and then we're always _sending_ a request and thus we wait for + the single socket to become writable only */ +static int http_getsock_do(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) +{ + /* write mode */ + (void)conn; + socks[0] = Curl_conn_get_socket(data, FIRSTSOCKET); + return GETSOCK_WRITESOCK(0); +} + +/* + * Curl_http_done() gets called after a single HTTP request has been + * performed. + */ + +CURLcode Curl_http_done(struct Curl_easy *data, + CURLcode status, bool premature) +{ + struct connectdata *conn = data->conn; + struct HTTP *http = data->req.p.http; + + /* Clear multipass flag. If authentication isn't done yet, then it will get + * a chance to be set back to true when we output the next auth header */ + data->state.authhost.multipass = FALSE; + data->state.authproxy.multipass = FALSE; + + /* set the proper values (possibly modified on POST) */ + conn->seek_func = data->set.seek_func; /* restore */ + conn->seek_client = data->set.seek_client; /* restore */ + + if(!http) + return CURLE_OK; + + Curl_dyn_free(&http->send_buffer); + Curl_dyn_reset(&data->state.headerb); + Curl_hyper_done(data); + Curl_ws_done(data); + + if(status) + return status; + + if(!premature && /* this check is pointless when DONE is called before the + entire operation is complete */ + !conn->bits.retry && + !data->set.connect_only && + (data->req.bytecount + + data->req.headerbytecount - + data->req.deductheadercount) <= 0) { + /* If this connection isn't simply closed to be retried, AND nothing was + read from the HTTP server (that counts), this can't be right so we + 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"); + return CURLE_GOT_NOTHING; + } + + return CURLE_OK; +} + +/* + * Determine if we should use HTTP 1.1 (OR BETTER) for this request. Reasons + * to avoid it include: + * + * - if the user specifically requested HTTP 1.0 + * - if the server we are connected to only supports 1.0 + * - if any server previously contacted to handle this request only supports + * 1.0. + */ +bool Curl_use_http_1_1plus(const struct Curl_easy *data, + const struct connectdata *conn) +{ + if((data->state.httpversion == 10) || (conn->httpversion == 10)) + return FALSE; + if((data->state.httpwant == CURL_HTTP_VERSION_1_0) && + (conn->httpversion <= 10)) + return FALSE; + return ((data->state.httpwant == CURL_HTTP_VERSION_NONE) || + (data->state.httpwant >= CURL_HTTP_VERSION_1_1)); +} + +#ifndef USE_HYPER +static const char *get_http_string(const struct Curl_easy *data, + const struct connectdata *conn) +{ + if(Curl_conn_is_http3(data, conn, FIRSTSOCKET)) + return "3"; + if(Curl_conn_is_http2(data, conn, FIRSTSOCKET)) + return "2"; + if(Curl_use_http_1_1plus(data, conn)) + return "1.1"; + + return "1.0"; +} +#endif + +/* check and possibly add an Expect: header */ +static CURLcode expect100(struct Curl_easy *data, + struct connectdata *conn, + struct dynbuf *req) +{ + CURLcode result = CURLE_OK; + if(!data->state.disableexpect && Curl_use_http_1_1plus(data, conn) && + (conn->httpversion < 20)) { + /* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an + Expect: 100-continue to the headers which actually speeds up post + operations (as there is one packet coming back from the web server) */ + const char *ptr = Curl_checkheaders(data, STRCONST("Expect")); + if(ptr) { + data->state.expect100header = + Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); + } + else { + result = Curl_dyn_addn(req, STRCONST("Expect: 100-continue\r\n")); + if(!result) + data->state.expect100header = TRUE; + } + } + + return result; +} + +enum proxy_use { + HEADER_SERVER, /* direct to server */ + HEADER_PROXY, /* regular request to proxy */ + HEADER_CONNECT /* sending CONNECT to a proxy */ +}; + +/* used to compile the provided trailers into one buffer + will return an error code if one of the headers is + not formatted correctly */ +CURLcode Curl_http_compile_trailers(struct curl_slist *trailers, + struct dynbuf *b, + struct Curl_easy *handle) +{ + char *ptr = NULL; + CURLcode result = CURLE_OK; + const char *endofline_native = NULL; + const char *endofline_network = NULL; + + if( +#ifdef CURL_DO_LINEEND_CONV + (handle->state.prefer_ascii) || +#endif + (handle->set.crlf)) { + /* \n will become \r\n later on */ + endofline_native = "\n"; + endofline_network = "\x0a"; + } + else { + endofline_native = "\r\n"; + endofline_network = "\x0d\x0a"; + } + + while(trailers) { + /* only add correctly formatted trailers */ + ptr = strchr(trailers->data, ':'); + if(ptr && *(ptr + 1) == ' ') { + result = Curl_dyn_add(b, trailers->data); + if(result) + return result; + result = Curl_dyn_add(b, endofline_native); + if(result) + return result; + } + else + infof(handle, "Malformatted trailing header, skipping trailer"); + trailers = trailers->next; + } + result = Curl_dyn_add(b, endofline_network); + return result; +} + +static bool hd_name_eq(const char *n1, size_t n1len, + const char *n2, size_t n2len) +{ + if(n1len == n2len) { + return strncasecompare(n1, n2, n1len); + } + return FALSE; +} + +CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, + bool is_connect, + struct dynhds *hds) +{ + struct connectdata *conn = data->conn; + char *ptr; + struct curl_slist *h[2]; + struct curl_slist *headers; + int numlists = 1; /* by default */ + int i; + +#ifndef CURL_DISABLE_PROXY + enum proxy_use proxy; + + if(is_connect) + proxy = HEADER_CONNECT; + else + proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy? + HEADER_PROXY:HEADER_SERVER; + + switch(proxy) { + case HEADER_SERVER: + h[0] = data->set.headers; + break; + case HEADER_PROXY: + h[0] = data->set.headers; + if(data->set.sep_headers) { + h[1] = data->set.proxyheaders; + numlists++; + } + break; + case HEADER_CONNECT: + if(data->set.sep_headers) + h[0] = data->set.proxyheaders; + else + h[0] = data->set.headers; + break; + } +#else + (void)is_connect; + h[0] = data->set.headers; +#endif + + /* loop through one or two lists */ + for(i = 0; i < numlists; i++) { + for(headers = h[i]; headers; headers = headers->next) { + const char *name, *value; + size_t namelen, valuelen; + + /* There are 2 quirks in place for custom headers: + * 1. setting only 'name:' to suppress a header from being sent + * 2. setting only 'name;' to send an empty (illegal) header + */ + ptr = strchr(headers->data, ':'); + if(ptr) { + name = headers->data; + namelen = ptr - headers->data; + ptr++; /* pass the colon */ + while(*ptr && ISSPACE(*ptr)) + ptr++; + if(*ptr) { + value = ptr; + valuelen = strlen(value); + } + else { + /* quirk #1, suppress this header */ + continue; + } + } + else { + ptr = strchr(headers->data, ';'); + + if(!ptr) { + /* neither : nor ; in provided header value. We seem + * to ignore this silently */ + continue; + } + + name = headers->data; + namelen = ptr - headers->data; + ptr++; /* pass the semicolon */ + while(*ptr && ISSPACE(*ptr)) + ptr++; + if(!*ptr) { + /* quirk #2, send an empty header */ + value = ""; + valuelen = 0; + } + else { + /* this may be used for something else in the future, + * ignore this for now */ + continue; + } + } + + DEBUGASSERT(name && value); + if(data->state.aptr.host && + /* a Host: header was sent already, don't pass on any custom Host: + header as that will produce *two* in the same request! */ + hd_name_eq(name, namelen, STRCONST("Host:"))) + ; + else if(data->state.httpreq == HTTPREQ_POST_FORM && + /* this header (extended by formdata.c) is sent later */ + hd_name_eq(name, namelen, STRCONST("Content-Type:"))) + ; + else if(data->state.httpreq == HTTPREQ_POST_MIME && + /* this header is sent later */ + hd_name_eq(name, namelen, STRCONST("Content-Type:"))) + ; + else if(conn->bits.authneg && + /* while doing auth neg, don't allow the custom length since + we will force length zero then */ + hd_name_eq(name, namelen, STRCONST("Content-Length:"))) + ; + else if(data->state.aptr.te && + /* when asking for Transfer-Encoding, don't pass on a custom + Connection: */ + hd_name_eq(name, namelen, STRCONST("Connection:"))) + ; + else if((conn->httpversion >= 20) && + hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:"))) + /* HTTP/2 doesn't support chunked requests */ + ; + else if((hd_name_eq(name, namelen, STRCONST("Authorization:")) || + hd_name_eq(name, namelen, STRCONST("Cookie:"))) && + /* be careful of sending this potentially sensitive header to + other hosts */ + !Curl_auth_allowed_to_host(data)) + ; + else { + CURLcode result; + + result = Curl_dynhds_add(hds, name, namelen, value, valuelen); + if(result) + return result; + } + } + } + + return CURLE_OK; +} + +CURLcode Curl_add_custom_headers(struct Curl_easy *data, + bool is_connect, +#ifndef USE_HYPER + struct dynbuf *req +#else + void *req +#endif + ) +{ + struct connectdata *conn = data->conn; + char *ptr; + struct curl_slist *h[2]; + struct curl_slist *headers; + int numlists = 1; /* by default */ + int i; + +#ifndef CURL_DISABLE_PROXY + enum proxy_use proxy; + + if(is_connect) + proxy = HEADER_CONNECT; + else + proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy? + HEADER_PROXY:HEADER_SERVER; + + switch(proxy) { + case HEADER_SERVER: + h[0] = data->set.headers; + break; + case HEADER_PROXY: + h[0] = data->set.headers; + if(data->set.sep_headers) { + h[1] = data->set.proxyheaders; + numlists++; + } + break; + case HEADER_CONNECT: + if(data->set.sep_headers) + h[0] = data->set.proxyheaders; + else + h[0] = data->set.headers; + break; + } +#else + (void)is_connect; + h[0] = data->set.headers; +#endif + + /* loop through one or two lists */ + for(i = 0; i < numlists; i++) { + headers = h[i]; + + while(headers) { + char *semicolonp = NULL; + ptr = strchr(headers->data, ':'); + if(!ptr) { + char *optr; + /* no colon, semicolon? */ + ptr = strchr(headers->data, ';'); + if(ptr) { + optr = ptr; + ptr++; /* pass the semicolon */ + while(*ptr && ISSPACE(*ptr)) + ptr++; + + if(*ptr) { + /* this may be used for something else in the future */ + optr = NULL; + } + else { + if(*(--ptr) == ';') { + /* copy the source */ + semicolonp = strdup(headers->data); + if(!semicolonp) { +#ifndef USE_HYPER + Curl_dyn_free(req); +#endif + return CURLE_OUT_OF_MEMORY; + } + /* put a colon where the semicolon is */ + semicolonp[ptr - headers->data] = ':'; + /* point at the colon */ + optr = &semicolonp [ptr - headers->data]; + } + } + ptr = optr; + } + } + if(ptr && (ptr != headers->data)) { + /* we require a colon for this to be a true header */ + + ptr++; /* pass the colon */ + while(*ptr && ISSPACE(*ptr)) + ptr++; + + if(*ptr || semicolonp) { + /* only send this if the contents was non-blank or done special */ + CURLcode result = CURLE_OK; + char *compare = semicolonp ? semicolonp : headers->data; + + if(data->state.aptr.host && + /* a Host: header was sent already, don't pass on any custom Host: + header as that will produce *two* in the same request! */ + checkprefix("Host:", compare)) + ; + else if(data->state.httpreq == HTTPREQ_POST_FORM && + /* this header (extended by formdata.c) is sent later */ + checkprefix("Content-Type:", compare)) + ; + else if(data->state.httpreq == HTTPREQ_POST_MIME && + /* this header is sent later */ + checkprefix("Content-Type:", compare)) + ; + else if(conn->bits.authneg && + /* while doing auth neg, don't allow the custom length since + we will force length zero then */ + checkprefix("Content-Length:", compare)) + ; + else if(data->state.aptr.te && + /* when asking for Transfer-Encoding, don't pass on a custom + Connection: */ + checkprefix("Connection:", compare)) + ; + else if((conn->httpversion >= 20) && + checkprefix("Transfer-Encoding:", compare)) + /* HTTP/2 doesn't support chunked requests */ + ; + else if((checkprefix("Authorization:", compare) || + checkprefix("Cookie:", compare)) && + /* be careful of sending this potentially sensitive header to + other hosts */ + !Curl_auth_allowed_to_host(data)) + ; + else { +#ifdef USE_HYPER + result = Curl_hyper_header(data, req, compare); +#else + result = Curl_dyn_addf(req, "%s\r\n", compare); +#endif + } + if(semicolonp) + free(semicolonp); + if(result) + return result; + } + } + headers = headers->next; + } + } + + return CURLE_OK; +} + +#ifndef CURL_DISABLE_PARSEDATE +CURLcode Curl_add_timecondition(struct Curl_easy *data, +#ifndef USE_HYPER + struct dynbuf *req +#else + void *req +#endif + ) +{ + const struct tm *tm; + struct tm keeptime; + CURLcode result; + char datestr[80]; + const char *condp; + size_t len; + + if(data->set.timecondition == CURL_TIMECOND_NONE) + /* no condition was asked for */ + return CURLE_OK; + + result = Curl_gmtime(data->set.timevalue, &keeptime); + if(result) { + failf(data, "Invalid TIMEVALUE"); + return result; + } + tm = &keeptime; + + switch(data->set.timecondition) { + default: + return CURLE_BAD_FUNCTION_ARGUMENT; + + case CURL_TIMECOND_IFMODSINCE: + condp = "If-Modified-Since"; + len = 17; + break; + case CURL_TIMECOND_IFUNMODSINCE: + condp = "If-Unmodified-Since"; + len = 19; + break; + case CURL_TIMECOND_LASTMOD: + condp = "Last-Modified"; + len = 13; + break; + } + + if(Curl_checkheaders(data, condp, len)) { + /* A custom header was specified; it will be sent instead. */ + return CURLE_OK; + } + + /* The If-Modified-Since header family should have their times set in + * GMT as RFC2616 defines: "All HTTP date/time stamps MUST be + * represented in Greenwich Mean Time (GMT), without exception. For the + * purposes of HTTP, GMT is exactly equal to UTC (Coordinated Universal + * Time)." (see page 20 of RFC2616). + */ + + /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ + msnprintf(datestr, sizeof(datestr), + "%s: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", + condp, + Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + +#ifndef USE_HYPER + result = Curl_dyn_add(req, datestr); +#else + result = Curl_hyper_header(data, req, datestr); +#endif + + return result; +} +#else +/* disabled */ +CURLcode Curl_add_timecondition(struct Curl_easy *data, + struct dynbuf *req) +{ + (void)data; + (void)req; + return CURLE_OK; +} +#endif + +void Curl_http_method(struct Curl_easy *data, struct connectdata *conn, + const char **method, Curl_HttpReq *reqp) +{ + Curl_HttpReq httpreq = (Curl_HttpReq)data->state.httpreq; + const char *request; + if((conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_FTP)) && + data->state.upload) + httpreq = HTTPREQ_PUT; + + /* Now set the 'request' pointer to the proper request string */ + if(data->set.str[STRING_CUSTOMREQUEST]) + request = data->set.str[STRING_CUSTOMREQUEST]; + else { + if(data->req.no_body) + request = "HEAD"; + else { + DEBUGASSERT((httpreq >= HTTPREQ_GET) && (httpreq <= HTTPREQ_HEAD)); + switch(httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + request = "POST"; + break; + case HTTPREQ_PUT: + request = "PUT"; + break; + default: /* this should never happen */ + case HTTPREQ_GET: + request = "GET"; + break; + case HTTPREQ_HEAD: + request = "HEAD"; + break; + } + } + } + *method = request; + *reqp = httpreq; +} + +CURLcode Curl_http_useragent(struct Curl_easy *data) +{ + /* The User-Agent string might have been allocated in url.c 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"))) { + free(data->state.aptr.uagent); + data->state.aptr.uagent = NULL; + } + return CURLE_OK; +} + + +CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn) +{ + const char *ptr; + struct dynamically_allocated_data *aptr = &data->state.aptr; + if(!data->state.this_is_a_follow) { + /* Free to avoid leaking memory on multiple requests */ + free(data->state.first_host); + + data->state.first_host = strdup(conn->host.name); + if(!data->state.first_host) + return CURLE_OUT_OF_MEMORY; + + data->state.first_remote_port = conn->remote_port; + data->state.first_remote_protocol = conn->handler->protocol; + } + Curl_safefree(aptr->host); + + ptr = Curl_checkheaders(data, STRCONST("Host")); + if(ptr && (!data->state.this_is_a_follow || + strcasecompare(data->state.first_host, conn->host.name))) { +#if !defined(CURL_DISABLE_COOKIES) + /* If we have a given custom Host: header, we extract the host name in + order to possibly use it for cookie reasons later on. We only allow the + custom Host: header if this is NOT a redirect, as setting Host: in the + redirected request is being out on thin ice. Except if the host name + is the same as the first one! */ + char *cookiehost = Curl_copy_header_value(ptr); + if(!cookiehost) + return CURLE_OUT_OF_MEMORY; + if(!*cookiehost) + /* ignore empty data */ + free(cookiehost); + else { + /* If the host begins with '[', we start searching for the port after + the bracket has been closed */ + if(*cookiehost == '[') { + char *closingbracket; + /* since the 'cookiehost' is an allocated memory area that will be + freed later we cannot simply increment the pointer */ + memmove(cookiehost, cookiehost + 1, strlen(cookiehost) - 1); + closingbracket = strchr(cookiehost, ']'); + if(closingbracket) + *closingbracket = 0; + } + else { + int startsearch = 0; + char *colon = strchr(cookiehost + startsearch, ':'); + if(colon) + *colon = 0; /* The host must not include an embedded port number */ + } + Curl_safefree(aptr->cookiehost); + aptr->cookiehost = cookiehost; + } +#endif + + if(strcmp("Host:", ptr)) { + aptr->host = aprintf("Host:%s\r\n", &ptr[5]); + if(!aptr->host) + return CURLE_OUT_OF_MEMORY; + } + } + else { + /* When building Host: headers, we must put the host name within + [brackets] if the host name is a plain IPv6-address. RFC2732-style. */ + const char *host = conn->host.name; + + if(((conn->given->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS)) && + (conn->remote_port == PORT_HTTPS)) || + ((conn->given->protocol&(CURLPROTO_HTTP|CURLPROTO_WS)) && + (conn->remote_port == PORT_HTTP)) ) + /* if(HTTPS on port 443) OR (HTTP on port 80) then don't include + the port number in the host string */ + aptr->host = aprintf("Host: %s%s%s\r\n", conn->bits.ipv6_ip?"[":"", + host, conn->bits.ipv6_ip?"]":""); + else + aptr->host = aprintf("Host: %s%s%s:%d\r\n", conn->bits.ipv6_ip?"[":"", + host, conn->bits.ipv6_ip?"]":"", + conn->remote_port); + + if(!aptr->host) + /* without Host: we can't make a nice request */ + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; +} + +/* + * Append the request-target to the HTTP request + */ +CURLcode Curl_http_target(struct Curl_easy *data, + struct connectdata *conn, + struct dynbuf *r) +{ + CURLcode result = CURLE_OK; + const char *path = data->state.up.path; + const char *query = data->state.up.query; + + if(data->set.str[STRING_TARGET]) { + path = data->set.str[STRING_TARGET]; + query = NULL; + } + +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + /* Using a proxy but does not tunnel through it */ + + /* 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 host name! */ + + /* and no fragment part */ + CURLUcode uc; + char *url; + CURLU *h = curl_url_dup(data->state.uh); + if(!h) + return CURLE_OUT_OF_MEMORY; + + if(conn->host.dispname != conn->host.name) { + uc = curl_url_set(h, CURLUPART_HOST, conn->host.name, 0); + if(uc) { + curl_url_cleanup(h); + return CURLE_OUT_OF_MEMORY; + } + } + uc = curl_url_set(h, CURLUPART_FRAGMENT, NULL, 0); + if(uc) { + curl_url_cleanup(h); + return CURLE_OUT_OF_MEMORY; + } + + if(strcasecompare("http", data->state.up.scheme)) { + /* when getting HTTP, we don't want the userinfo the URL */ + uc = curl_url_set(h, CURLUPART_USER, NULL, 0); + if(uc) { + curl_url_cleanup(h); + return CURLE_OUT_OF_MEMORY; + } + uc = curl_url_set(h, CURLUPART_PASSWORD, NULL, 0); + if(uc) { + curl_url_cleanup(h); + return CURLE_OUT_OF_MEMORY; + } + } + /* Extract the URL to use in the request. Store in STRING_TEMP_URL for + clean-up reasons if the function returns before the free() further + down. */ + uc = curl_url_get(h, CURLUPART_URL, &url, CURLU_NO_DEFAULT_PORT); + if(uc) { + curl_url_cleanup(h); + return CURLE_OUT_OF_MEMORY; + } + + curl_url_cleanup(h); + + /* target or url */ + result = Curl_dyn_add(r, data->set.str[STRING_TARGET]? + data->set.str[STRING_TARGET]:url); + free(url); + if(result) + return (result); + + if(strcasecompare("ftp", data->state.up.scheme)) { + if(data->set.proxy_transfer_mode) { + /* when doing ftp, append ;type=<a|i> if not present */ + char *type = strstr(path, ";type="); + if(type && type[6] && type[7] == 0) { + switch(Curl_raw_toupper(type[6])) { + case 'A': + case 'D': + case 'I': + break; + default: + type = NULL; + } + } + if(!type) { + result = Curl_dyn_addf(r, ";type=%c", + data->state.prefer_ascii ? 'a' : 'i'); + if(result) + return result; + } + } + } + } + + else +#else + (void)conn; /* not used in disabled-proxy builds */ +#endif + { + result = Curl_dyn_add(r, path); + if(result) + return result; + if(query) + result = Curl_dyn_addf(r, "?%s", query); + } + + return result; +} + +CURLcode Curl_http_body(struct Curl_easy *data, struct connectdata *conn, + Curl_HttpReq httpreq, const char **tep) +{ + CURLcode result = CURLE_OK; + const char *ptr; + struct HTTP *http = data->req.p.http; + http->postsize = 0; + + switch(httpreq) { + case HTTPREQ_POST_MIME: + data->state.mimepost = &data->set.mimepost; + break; +#ifndef CURL_DISABLE_FORM_API + case HTTPREQ_POST_FORM: + /* Convert the form structure into a mime structure, then keep + the conversion */ + if(!data->state.formp) { + data->state.formp = calloc(1, sizeof(curl_mimepart)); + if(!data->state.formp) + return CURLE_OUT_OF_MEMORY; + Curl_mime_cleanpart(data->state.formp); + result = Curl_getformdata(data, data->state.formp, data->set.httppost, + data->state.fread_func); + if(result) { + Curl_safefree(data->state.formp); + return result; + } + data->state.mimepost = data->state.formp; + } + break; +#endif + default: + data->state.mimepost = NULL; + } + +#ifndef CURL_DISABLE_MIME + if(data->state.mimepost) { + const char *cthdr = Curl_checkheaders(data, STRCONST("Content-Type")); + + /* Read and seek body only. */ + data->state.mimepost->flags |= MIME_BODY_ONLY; + + /* Prepare the mime structure headers & set content type. */ + + if(cthdr) + for(cthdr += 13; *cthdr == ' '; cthdr++) + ; + else if(data->state.mimepost->kind == MIMEKIND_MULTIPART) + cthdr = "multipart/form-data"; + + curl_mime_headers(data->state.mimepost, data->set.headers, 0); + result = Curl_mime_prepare_headers(data, data->state.mimepost, cthdr, + NULL, MIMESTRATEGY_FORM); + curl_mime_headers(data->state.mimepost, NULL, 0); + if(!result) + result = Curl_mime_rewind(data->state.mimepost); + if(result) + return result; + http->postsize = Curl_mime_size(data->state.mimepost); + } +#endif + + ptr = Curl_checkheaders(data, STRCONST("Transfer-Encoding")); + if(ptr) { + /* Some kind of TE is requested, check if 'chunked' is chosen */ + data->req.upload_chunky = + Curl_compareheader(ptr, + STRCONST("Transfer-Encoding:"), STRCONST("chunked")); + } + else { + if((conn->handler->protocol & PROTO_FAMILY_HTTP) && + (((httpreq == HTTPREQ_POST_MIME || httpreq == HTTPREQ_POST_FORM) && + http->postsize < 0) || + ((data->state.upload || httpreq == HTTPREQ_POST) && + data->state.infilesize == -1))) { + if(conn->bits.authneg) + /* don't enable chunked during auth neg */ + ; + else if(Curl_use_http_1_1plus(data, conn)) { + if(conn->httpversion < 20) + /* HTTP, upload, unknown file size and not HTTP 1.0 */ + data->req.upload_chunky = TRUE; + } + else { + failf(data, "Chunky upload is not supported by HTTP 1.0"); + return CURLE_UPLOAD_FAILED; + } + } + else { + /* else, no chunky upload */ + data->req.upload_chunky = FALSE; + } + + if(data->req.upload_chunky) + *tep = "Transfer-Encoding: chunked\r\n"; + } + return result; +} + +static CURLcode addexpect(struct Curl_easy *data, struct connectdata *conn, + struct dynbuf *r) +{ + data->state.expect100header = FALSE; + /* Avoid Expect: 100-continue if Upgrade: is used */ + if(data->req.upgr101 == UPGR101_INIT) { + struct HTTP *http = data->req.p.http; + /* For really small puts we don't use Expect: headers at all, and for + the somewhat bigger ones we allow the app to disable it. Just make + sure that the expect100header is always set to the preferred value + here. */ + char *ptr = Curl_checkheaders(data, STRCONST("Expect")); + if(ptr) { + data->state.expect100header = + Curl_compareheader(ptr, STRCONST("Expect:"), + STRCONST("100-continue")); + } + else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) + return expect100(data, conn, r); + } + return CURLE_OK; +} + +CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, + struct dynbuf *r, Curl_HttpReq httpreq) +{ +#ifndef USE_HYPER + /* Hyper always handles the body separately */ + curl_off_t included_body = 0; +#else + /* from this point down, this function should not be used */ +#define Curl_buffer_send(a,b,c,d,e,f) CURLE_OK +#endif + CURLcode result = CURLE_OK; + struct HTTP *http = data->req.p.http; + + switch(httpreq) { + case HTTPREQ_PUT: /* Let's PUT the data to the server! */ + + if(conn->bits.authneg) + http->postsize = 0; + else + http->postsize = data->state.infilesize; + + if((http->postsize != -1) && !data->req.upload_chunky && + (conn->bits.authneg || + !Curl_checkheaders(data, STRCONST("Content-Length")))) { + /* only add Content-Length if not uploading chunked */ + result = Curl_dyn_addf(r, "Content-Length: %" CURL_FORMAT_CURL_OFF_T + "\r\n", http->postsize); + if(result) + return result; + } + + result = addexpect(data, conn, r); + if(result) + return result; + + /* end of headers */ + result = Curl_dyn_addn(r, STRCONST("\r\n")); + if(result) + return result; + + /* set the upload size to the progress meter */ + Curl_pgrsSetUploadSize(data, http->postsize); + + /* this sends the buffer and frees all the buffer resources */ + result = Curl_buffer_send(r, data, data->req.p.http, + &data->info.request_size, 0, + FIRSTSOCKET); + if(result) + failf(data, "Failed sending PUT request"); + else + /* prepare for transfer */ + Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, + http->postsize?FIRSTSOCKET:-1); + if(result) + return result; + break; + + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + /* This is form posting using mime data. */ + if(conn->bits.authneg) { + /* nothing to post! */ + result = Curl_dyn_addn(r, STRCONST("Content-Length: 0\r\n\r\n")); + if(result) + return result; + + result = Curl_buffer_send(r, data, data->req.p.http, + &data->info.request_size, 0, + FIRSTSOCKET); + if(result) + failf(data, "Failed sending POST request"); + else + /* setup variables for the upcoming transfer */ + Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); + break; + } + + data->state.infilesize = http->postsize; + + /* We only set Content-Length and allow a custom Content-Length if + we don't upload data chunked, as RFC2616 forbids us to set both + kinds of headers (Transfer-Encoding: chunked and Content-Length) */ + if(http->postsize != -1 && !data->req.upload_chunky && + (!Curl_checkheaders(data, STRCONST("Content-Length")))) { + /* we allow replacing this header if not during auth negotiation, + although it isn't very wise to actually set your own */ + result = Curl_dyn_addf(r, + "Content-Length: %" CURL_FORMAT_CURL_OFF_T + "\r\n", http->postsize); + if(result) + return result; + } + +#ifndef CURL_DISABLE_MIME + /* Output mime-generated headers. */ + { + struct curl_slist *hdr; + + for(hdr = data->state.mimepost->curlheaders; hdr; hdr = hdr->next) { + result = Curl_dyn_addf(r, "%s\r\n", hdr->data); + if(result) + return result; + } + } +#endif + + result = addexpect(data, conn, r); + if(result) + return result; + + /* make the request end in a true CRLF */ + result = Curl_dyn_addn(r, STRCONST("\r\n")); + if(result) + return result; + + /* set the upload size to the progress meter */ + Curl_pgrsSetUploadSize(data, http->postsize); + + /* Read from mime structure. */ + data->state.fread_func = (curl_read_callback) Curl_mime_read; + data->state.in = (void *) data->state.mimepost; + http->sending = HTTPSEND_BODY; + + /* this sends the buffer and frees all the buffer resources */ + result = Curl_buffer_send(r, data, data->req.p.http, + &data->info.request_size, 0, + FIRSTSOCKET); + if(result) + failf(data, "Failed sending POST request"); + else + /* prepare for transfer */ + Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, + http->postsize?FIRSTSOCKET:-1); + if(result) + return result; + + break; + + case HTTPREQ_POST: + /* this is the simple POST, using x-www-form-urlencoded style */ + + if(conn->bits.authneg) + http->postsize = 0; + else + /* the size of the post body */ + http->postsize = data->state.infilesize; + + /* We only set Content-Length and allow a custom Content-Length if + we don't upload data chunked, as RFC2616 forbids us to set both + kinds of headers (Transfer-Encoding: chunked and Content-Length) */ + if((http->postsize != -1) && !data->req.upload_chunky && + (conn->bits.authneg || + !Curl_checkheaders(data, STRCONST("Content-Length")))) { + /* we allow replacing this header if not during auth negotiation, + although it isn't very wise to actually set your own */ + result = Curl_dyn_addf(r, "Content-Length: %" CURL_FORMAT_CURL_OFF_T + "\r\n", http->postsize); + if(result) + return result; + } + + if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { + result = Curl_dyn_addn(r, STRCONST("Content-Type: application/" + "x-www-form-urlencoded\r\n")); + if(result) + return result; + } + + result = addexpect(data, conn, r); + if(result) + return result; + +#ifndef USE_HYPER + /* With Hyper the body is always passed on separately */ + if(data->set.postfields) { + if(!data->state.expect100header && + (http->postsize < MAX_INITIAL_POST_SIZE)) { + /* if we don't use expect: 100 AND + postsize is less than MAX_INITIAL_POST_SIZE + + then append the post data to the HTTP request header. This limit + is no magic limit but only set to prevent really huge POSTs to + get the data duplicated with malloc() and family. */ + + /* end of headers! */ + result = Curl_dyn_addn(r, STRCONST("\r\n")); + if(result) + return result; + + if(!data->req.upload_chunky) { + /* We're not sending it 'chunked', append it to the request + already now to reduce the number of send() calls */ + result = Curl_dyn_addn(r, data->set.postfields, + (size_t)http->postsize); + included_body = http->postsize; + } + else { + if(http->postsize) { + char chunk[16]; + /* Append the POST data chunky-style */ + msnprintf(chunk, sizeof(chunk), "%x\r\n", (int)http->postsize); + result = Curl_dyn_add(r, chunk); + if(!result) { + included_body = http->postsize + strlen(chunk); + result = Curl_dyn_addn(r, data->set.postfields, + (size_t)http->postsize); + if(!result) + result = Curl_dyn_addn(r, STRCONST("\r\n")); + included_body += 2; + } + } + if(!result) { + result = Curl_dyn_addn(r, STRCONST("\x30\x0d\x0a\x0d\x0a")); + /* 0 CR LF CR LF */ + included_body += 5; + } + } + if(result) + return result; + /* Make sure the progress information is accurate */ + Curl_pgrsSetUploadSize(data, http->postsize); + } + else { + /* A huge POST coming up, do data separate from the request */ + http->postdata = data->set.postfields; + http->sending = HTTPSEND_BODY; + http->backup.data = data; + data->state.fread_func = (curl_read_callback)readmoredata; + data->state.in = (void *)http; + + /* set the upload size to the progress meter */ + Curl_pgrsSetUploadSize(data, http->postsize); + + /* end of headers! */ + result = Curl_dyn_addn(r, STRCONST("\r\n")); + if(result) + return result; + } + } + else +#endif + { + /* end of headers! */ + result = Curl_dyn_addn(r, STRCONST("\r\n")); + if(result) + return result; + + if(data->req.upload_chunky && conn->bits.authneg) { + /* Chunky upload is selected and we're negotiating auth still, send + end-of-data only */ + result = Curl_dyn_addn(r, (char *)STRCONST("\x30\x0d\x0a\x0d\x0a")); + /* 0 CR LF CR LF */ + if(result) + return result; + } + + else if(data->state.infilesize) { + /* set the upload size to the progress meter */ + Curl_pgrsSetUploadSize(data, http->postsize?http->postsize:-1); + + /* set the pointer to mark that we will send the post body using the + read callback, but only if we're not in authenticate negotiation */ + if(!conn->bits.authneg) + http->postdata = (char *)&http->postdata; + } + } + /* issue the request */ + result = Curl_buffer_send(r, data, data->req.p.http, + &data->info.request_size, included_body, + FIRSTSOCKET); + + if(result) + failf(data, "Failed sending HTTP POST request"); + else + Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, + http->postdata?FIRSTSOCKET:-1); + break; + + default: + result = Curl_dyn_addn(r, STRCONST("\r\n")); + if(result) + return result; + + /* issue the request */ + result = Curl_buffer_send(r, data, data->req.p.http, + &data->info.request_size, 0, + FIRSTSOCKET); + if(result) + failf(data, "Failed sending HTTP request"); +#ifdef USE_WEBSOCKETS + else if((conn->handler->protocol & (CURLPROTO_WS|CURLPROTO_WSS)) && + !(data->set.connect_only)) + /* Set up the transfer for two-way since without CONNECT_ONLY set, this + request probably wants to send data too post upgrade */ + Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, FIRSTSOCKET); +#endif + else + /* HTTP GET/HEAD download: */ + Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); + } + + return result; +} + +#if !defined(CURL_DISABLE_COOKIES) + +CURLcode Curl_http_cookies(struct Curl_easy *data, + struct connectdata *conn, + struct dynbuf *r) +{ + CURLcode result = CURLE_OK; + char *addcookies = NULL; + bool linecap = FALSE; + if(data->set.str[STRING_COOKIE] && + !Curl_checkheaders(data, STRCONST("Cookie"))) + addcookies = data->set.str[STRING_COOKIE]; + + if(data->cookies || addcookies) { + struct Cookie *co = NULL; /* no cookies from start */ + int count = 0; + + if(data->cookies && data->state.cookie_engine) { + const char *host = data->state.aptr.cookiehost ? + data->state.aptr.cookiehost : conn->host.name; + const bool secure_context = + conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) || + strcasecompare("localhost", host) || + !strcmp(host, "127.0.0.1") || + !strcmp(host, "::1") ? TRUE : FALSE; + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + co = Curl_cookie_getlist(data, data->cookies, host, data->state.up.path, + secure_context); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } + if(co) { + struct Cookie *store = co; + size_t clen = 8; /* hold the size of the generated Cookie: header */ + /* now loop through all cookies that matched */ + while(co) { + if(co->value) { + size_t add; + if(!count) { + result = Curl_dyn_addn(r, STRCONST("Cookie: ")); + if(result) + break; + } + add = strlen(co->name) + strlen(co->value) + 1; + if(clen + add >= MAX_COOKIE_HEADER_LEN) { + infof(data, "Restricted outgoing cookies due to header size, " + "'%s' not sent", co->name); + linecap = TRUE; + break; + } + result = Curl_dyn_addf(r, "%s%s=%s", count?"; ":"", + co->name, co->value); + if(result) + break; + clen += add + (count ? 2 : 0); + count++; + } + co = co->next; /* next cookie please */ + } + Curl_cookie_freelist(store); + } + if(addcookies && !result && !linecap) { + if(!count) + result = Curl_dyn_addn(r, STRCONST("Cookie: ")); + if(!result) { + result = Curl_dyn_addf(r, "%s%s", count?"; ":"", addcookies); + count++; + } + } + if(count && !result) + result = Curl_dyn_addn(r, STRCONST("\r\n")); + + if(result) + return result; + } + return result; +} +#endif + +CURLcode Curl_http_range(struct Curl_easy *data, + Curl_HttpReq httpreq) +{ + if(data->state.use_range) { + /* + * A range is selected. We use different headers whether we're downloading + * or uploading and we always let customized headers override our internal + * ones if any such are specified. + */ + if(((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) && + !Curl_checkheaders(data, STRCONST("Range"))) { + /* if a line like this was already allocated, free the previous one */ + free(data->state.aptr.rangeline); + data->state.aptr.rangeline = aprintf("Range: bytes=%s\r\n", + data->state.range); + } + else if((httpreq == HTTPREQ_POST || httpreq == HTTPREQ_PUT) && + !Curl_checkheaders(data, STRCONST("Content-Range"))) { + + /* if a line like this was already allocated, free the previous one */ + free(data->state.aptr.rangeline); + + if(data->set.set_resume_from < 0) { + /* Upload resume was asked for, but we don't 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 = + aprintf("Content-Range: bytes 0-%" CURL_FORMAT_CURL_OFF_T + "/%" CURL_FORMAT_CURL_OFF_T "\r\n", + data->state.infilesize - 1, data->state.infilesize); + + } + else if(data->state.resume_from) { + /* This is because "resume" was selected */ + curl_off_t total_expected_size = + data->state.resume_from + data->state.infilesize; + data->state.aptr.rangeline = + aprintf("Content-Range: bytes %s%" CURL_FORMAT_CURL_OFF_T + "/%" CURL_FORMAT_CURL_OFF_T "\r\n", + data->state.range, total_expected_size-1, + total_expected_size); + } + else { + /* Range was selected and then we just pass the incoming range and + append total size */ + data->state.aptr.rangeline = + aprintf("Content-Range: bytes %s/%" CURL_FORMAT_CURL_OFF_T "\r\n", + data->state.range, data->state.infilesize); + } + if(!data->state.aptr.rangeline) + return CURLE_OUT_OF_MEMORY; + } + } + return CURLE_OK; +} + +CURLcode Curl_http_resume(struct Curl_easy *data, + struct connectdata *conn, + Curl_HttpReq httpreq) +{ + if((HTTPREQ_POST == httpreq || HTTPREQ_PUT == httpreq) && + data->state.resume_from) { + /********************************************************************** + * Resuming upload in HTTP means that we PUT or POST and that we have + * got a resume_from value set. The resume value has already created + * a Range: header that will be passed along. We need to "fast forward" + * the file the given number of bytes and decrease the assume upload + * file size before we continue this venture in the dark lands of HTTP. + * Resuming mime/form posting at an offset > 0 has no sense and is ignored. + *********************************************************************/ + + if(data->state.resume_from < 0) { + /* + * This is meant to get the size of the present remote-file by itself. + * We don't support this now. Bail out! + */ + data->state.resume_from = 0; + } + + if(data->state.resume_from && !data->state.followlocation) { + /* only act on the first request */ + + /* Now, let's read off the proper amount of bytes from the + input. */ + int seekerr = CURL_SEEKFUNC_CANTSEEK; + if(conn->seek_func) { + Curl_set_in_callback(data, true); + seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, + SEEK_SET); + Curl_set_in_callback(data, false); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; + + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_READ_ERROR; + } + /* when seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + do { + size_t readthisamountnow = + (data->state.resume_from - passed > data->set.buffer_size) ? + (size_t)data->set.buffer_size : + curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread = + data->state.fread_func(data->state.buffer, 1, readthisamountnow, + data->state.in); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Could only read %" CURL_FORMAT_CURL_OFF_T + " bytes from the input", passed); + return CURLE_READ_ERROR; + } + } while(passed < data->state.resume_from); + } + + /* now, decrease the size of the read */ + if(data->state.infilesize>0) { + data->state.infilesize -= data->state.resume_from; + + if(data->state.infilesize <= 0) { + failf(data, "File already completely uploaded"); + return CURLE_PARTIAL_FILE; + } + } + /* we've passed, proceed as normal */ + } + } + return CURLE_OK; +} + +CURLcode Curl_http_firstwrite(struct Curl_easy *data, + struct connectdata *conn, + bool *done) +{ + struct SingleRequest *k = &data->req; + + if(data->req.newurl) { + if(conn->bits.close) { + /* Abort after the headers if "follow Location" is set + and we're set to close anyway. */ + k->keepon &= ~KEEP_RECV; + *done = TRUE; + return CURLE_OK; + } + /* We have a new url to load, but since we want to be able to reuse this + connection properly, we read the full response in "ignore more" */ + k->ignorebody = TRUE; + infof(data, "Ignoring the response-body"); + } + if(data->state.resume_from && !k->content_range && + (data->state.httpreq == HTTPREQ_GET) && + !k->ignorebody) { + + if(k->size == data->state.resume_from) { + /* The resume point is at the end of file, consider this fine even if it + doesn't allow resume from here. */ + infof(data, "The entire document is already downloaded"); + streamclose(conn, "already downloaded"); + /* Abort download */ + k->keepon &= ~KEEP_RECV; + *done = TRUE; + return CURLE_OK; + } + + /* we wanted to resume a download, although the server doesn't seem to + * support this and we did this with a GET (if it wasn't a GET we did a + * POST or PUT resume) */ + failf(data, "HTTP server doesn't seem to support " + "byte ranges. Cannot resume."); + return CURLE_RANGE_ERROR; + } + + if(data->set.timecondition && !data->state.range) { + /* A time condition has been set AND no ranges have been requested. This + seems to be what chapter 13.3.4 of RFC 2616 defines to be the correct + action for an HTTP/1.1 client */ + + if(!Curl_meets_timecondition(data, k->timeofdoc)) { + *done = TRUE; + /* We're simulating an HTTP 304 from server so we return + what should have been returned from the server */ + data->info.httpcode = 304; + 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"); + return CURLE_OK; + } + } /* we have a time condition */ + + return CURLE_OK; +} + +#ifdef HAVE_LIBZ +CURLcode Curl_transferencode(struct Curl_easy *data) +{ + if(!Curl_checkheaders(data, STRCONST("TE")) && + data->set.http_transfer_encoding) { + /* When we are to insert a TE: header in the request, we must also insert + TE in a Connection: header, so we need to merge the custom provided + Connection: header and prevent the original to get sent. Note that if + the user has inserted his/her own TE: header we don't do this magic + but then assume that the user will handle it all! */ + char *cptr = Curl_checkheaders(data, STRCONST("Connection")); +#define TE_HEADER "TE: gzip\r\n" + + Curl_safefree(data->state.aptr.te); + + if(cptr) { + cptr = Curl_copy_header_value(cptr); + if(!cptr) + return CURLE_OUT_OF_MEMORY; + } + + /* Create the (updated) Connection: header */ + data->state.aptr.te = aprintf("Connection: %s%sTE\r\n" TE_HEADER, + cptr ? cptr : "", (cptr && *cptr) ? ", ":""); + + free(cptr); + if(!data->state.aptr.te) + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; +} +#endif + +#ifndef USE_HYPER +/* + * Curl_http() gets called from the generic multi_do() function when an HTTP + * request is to be performed. This creates and sends a properly constructed + * HTTP request. + */ +CURLcode Curl_http(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; + struct HTTP *http; + Curl_HttpReq httpreq; + const char *te = ""; /* transfer-encoding */ + const char *request; + const char *httpstring; + struct dynbuf req; + char *altused = NULL; + const char *p_accept; /* Accept: string */ + + /* Always consider the DO phase done after this function call, even if there + may be parts of the request that are not yet sent, since we can deal with + the rest of the request in the PERFORM phase. */ + *done = TRUE; + + switch(conn->alpn) { + case CURL_HTTP_VERSION_3: + DEBUGASSERT(Curl_conn_is_http3(data, conn, FIRSTSOCKET)); + break; + case CURL_HTTP_VERSION_2: +#ifndef CURL_DISABLE_PROXY + if(!Curl_conn_is_http2(data, conn, FIRSTSOCKET) && + conn->bits.proxy && !conn->bits.tunnel_proxy + ) { + result = Curl_http2_switch(data, conn, FIRSTSOCKET); + if(result) + return result; + } + else +#endif + DEBUGASSERT(Curl_conn_is_http2(data, conn, FIRSTSOCKET)); + break; + case CURL_HTTP_VERSION_1_1: + /* continue with HTTP/1.x when explicitly requested */ + break; + default: + /* Check if user wants to use HTTP/2 with clear TCP */ + if(Curl_http2_may_switch(data, conn, FIRSTSOCKET)) { + DEBUGF(infof(data, "HTTP/2 over clean TCP")); + result = Curl_http2_switch(data, conn, FIRSTSOCKET); + if(result) + return result; + } + break; + } + + http = data->req.p.http; + DEBUGASSERT(http); + + result = Curl_http_host(data, conn); + if(result) + return result; + + result = Curl_http_useragent(data); + if(result) + return result; + + Curl_http_method(data, conn, &request, &httpreq); + + /* setup the authentication headers */ + { + char *pq = NULL; + if(data->state.up.query) { + pq = aprintf("%s?%s", data->state.up.path, data->state.up.query); + if(!pq) + return CURLE_OUT_OF_MEMORY; + } + result = Curl_http_output_auth(data, conn, request, httpreq, + (pq ? pq : data->state.up.path), FALSE); + free(pq); + if(result) + return result; + } + + Curl_safefree(data->state.aptr.ref); + if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer"))) { + data->state.aptr.ref = aprintf("Referer: %s\r\n", data->state.referer); + if(!data->state.aptr.ref) + return CURLE_OUT_OF_MEMORY; + } + + if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && + data->set.str[STRING_ENCODING]) { + Curl_safefree(data->state.aptr.accept_encoding); + data->state.aptr.accept_encoding = + aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); + if(!data->state.aptr.accept_encoding) + return CURLE_OUT_OF_MEMORY; + } + else + Curl_safefree(data->state.aptr.accept_encoding); + +#ifdef HAVE_LIBZ + /* we only consider transfer-encoding magic if libz support is built-in */ + result = Curl_transferencode(data); + if(result) + return result; +#endif + + result = Curl_http_body(data, conn, httpreq, &te); + if(result) + return result; + + p_accept = Curl_checkheaders(data, + STRCONST("Accept"))?NULL:"Accept: */*\r\n"; + + result = Curl_http_resume(data, conn, httpreq); + if(result) + return result; + + result = Curl_http_range(data, httpreq); + if(result) + return result; + + httpstring = get_http_string(data, conn); + + /* initialize a dynamic send-buffer */ + Curl_dyn_init(&req, DYN_HTTP_REQUEST); + + /* make sure the header buffer is reset - if there are leftovers from a + previous transfer */ + Curl_dyn_reset(&data->state.headerb); + + /* add the main request stuff */ + /* GET/HEAD/POST/PUT */ + result = Curl_dyn_addf(&req, "%s ", request); + if(!result) + result = Curl_http_target(data, conn, &req); + if(result) { + Curl_dyn_free(&req); + return result; + } + +#ifndef CURL_DISABLE_ALTSVC + if(conn->bits.altused && !Curl_checkheaders(data, STRCONST("Alt-Used"))) { + altused = aprintf("Alt-Used: %s:%d\r\n", + conn->conn_to_host.name, conn->conn_to_port); + if(!altused) { + Curl_dyn_free(&req); + return CURLE_OUT_OF_MEMORY; + } + } +#endif + result = + Curl_dyn_addf(&req, + " HTTP/%s\r\n" /* HTTP version */ + "%s" /* host */ + "%s" /* proxyuserpwd */ + "%s" /* userpwd */ + "%s" /* range */ + "%s" /* user agent */ + "%s" /* accept */ + "%s" /* TE: */ + "%s" /* accept-encoding */ + "%s" /* referer */ + "%s" /* Proxy-Connection */ + "%s" /* transfer-encoding */ + "%s",/* Alt-Used */ + + httpstring, + (data->state.aptr.host?data->state.aptr.host:""), + data->state.aptr.proxyuserpwd? + data->state.aptr.proxyuserpwd:"", + data->state.aptr.userpwd?data->state.aptr.userpwd:"", + (data->state.use_range && data->state.aptr.rangeline)? + data->state.aptr.rangeline:"", + (data->set.str[STRING_USERAGENT] && + *data->set.str[STRING_USERAGENT] && + data->state.aptr.uagent)? + data->state.aptr.uagent:"", + p_accept?p_accept:"", + data->state.aptr.te?data->state.aptr.te:"", + (data->set.str[STRING_ENCODING] && + *data->set.str[STRING_ENCODING] && + data->state.aptr.accept_encoding)? + data->state.aptr.accept_encoding:"", + (data->state.referer && data->state.aptr.ref)? + data->state.aptr.ref:"" /* Referer: <data> */, +#ifndef CURL_DISABLE_PROXY + (conn->bits.httpproxy && + !conn->bits.tunnel_proxy && + !Curl_checkheaders(data, STRCONST("Proxy-Connection")) && + !Curl_checkProxyheaders(data, + conn, + STRCONST("Proxy-Connection")))? + "Proxy-Connection: Keep-Alive\r\n":"", +#else + "", +#endif + te, + altused ? altused : "" + ); + + /* clear userpwd and proxyuserpwd to avoid reusing old credentials + * from reused connections */ + Curl_safefree(data->state.aptr.userpwd); + Curl_safefree(data->state.aptr.proxyuserpwd); + free(altused); + + if(result) { + Curl_dyn_free(&req); + return result; + } + + if(!(conn->handler->flags&PROTOPT_SSL) && + conn->httpversion < 20 && + (data->state.httpwant == CURL_HTTP_VERSION_2)) { + /* append HTTP2 upgrade magic stuff to the HTTP request if it isn't done + over SSL */ + result = Curl_http2_request_upgrade(&req, data); + if(result) { + Curl_dyn_free(&req); + return result; + } + } + + result = Curl_http_cookies(data, conn, &req); +#ifdef USE_WEBSOCKETS + if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) + result = Curl_ws_request(data, &req); +#endif + if(!result) + result = Curl_add_timecondition(data, &req); + if(!result) + result = Curl_add_custom_headers(data, FALSE, &req); + + if(!result) { + http->postdata = NULL; /* nothing to post at this point */ + if((httpreq == HTTPREQ_GET) || + (httpreq == HTTPREQ_HEAD)) + Curl_pgrsSetUploadSize(data, 0); /* nothing */ + + /* bodysend takes ownership of the 'req' memory on success */ + result = Curl_http_bodysend(data, conn, &req, httpreq); + } + if(result) { + Curl_dyn_free(&req); + return result; + } + + if((http->postsize > -1) && + (http->postsize <= data->req.writebytecount) && + (http->sending != HTTPSEND_REQUEST)) + data->req.upload_done = TRUE; + + if(data->req.writebytecount) { + /* if a request-body has been sent off, we make sure this progress is noted + properly */ + Curl_pgrsSetUploadCounter(data, data->req.writebytecount); + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + + if(!http->postsize) { + /* already sent the entire request body, mark the "upload" as + complete */ + infof(data, "upload completely sent off: %" CURL_FORMAT_CURL_OFF_T + " out of %" CURL_FORMAT_CURL_OFF_T " bytes", + data->req.writebytecount, http->postsize); + data->req.upload_done = TRUE; + data->req.keepon &= ~KEEP_SEND; /* we're done writing */ + data->req.exp100 = EXP100_SEND_DATA; /* already sent */ + Curl_expire_done(data, EXPIRE_100_TIMEOUT); + } + } + + if(data->req.upload_done) + Curl_conn_ev_data_done_send(data); + + if((conn->httpversion >= 20) && data->req.upload_chunky) + /* upload_chunky was set above to set up the request in a chunky fashion, + but is disabled here again to avoid that the chunked encoded version is + actually used when sending the request body over h2 */ + data->req.upload_chunky = FALSE; + return result; +} + +#endif /* USE_HYPER */ + +typedef enum { + STATUS_UNKNOWN, /* not enough data to tell yet */ + STATUS_DONE, /* a status line was read */ + STATUS_BAD /* not a status line */ +} statusline; + + +/* Check a string for a prefix. Check no more than 'len' bytes */ +static bool checkprefixmax(const char *prefix, const char *buffer, size_t len) +{ + size_t ch = CURLMIN(strlen(prefix), len); + return curl_strnequal(prefix, buffer, ch); +} + +/* + * checkhttpprefix() + * + * Returns TRUE if member of the list matches prefix of string + */ +static statusline +checkhttpprefix(struct Curl_easy *data, + const char *s, size_t len) +{ + struct curl_slist *head = data->set.http200aliases; + statusline rc = STATUS_BAD; + statusline onmatch = len >= 5? STATUS_DONE : STATUS_UNKNOWN; + + while(head) { + if(checkprefixmax(head->data, s, len)) { + rc = onmatch; + break; + } + head = head->next; + } + + if((rc != STATUS_DONE) && (checkprefixmax("HTTP/", s, len))) + rc = onmatch; + + return rc; +} + +#ifndef CURL_DISABLE_RTSP +static statusline +checkrtspprefix(struct Curl_easy *data, + const char *s, size_t len) +{ + statusline result = STATUS_BAD; + statusline onmatch = len >= 5? STATUS_DONE : STATUS_UNKNOWN; + (void)data; /* unused */ + if(checkprefixmax("RTSP/", s, len)) + result = onmatch; + + return result; +} +#endif /* CURL_DISABLE_RTSP */ + +static statusline +checkprotoprefix(struct Curl_easy *data, struct connectdata *conn, + const char *s, size_t len) +{ +#ifndef CURL_DISABLE_RTSP + if(conn->handler->protocol & CURLPROTO_RTSP) + return checkrtspprefix(data, s, len); +#else + (void)conn; +#endif /* CURL_DISABLE_RTSP */ + + return checkhttpprefix(data, s, len); +} + +/* + * Curl_http_header() parses a single response header. + */ +CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, + char *headp) +{ + CURLcode result; + struct SingleRequest *k = &data->req; + /* Check for Content-Length: header lines to get size */ + if(!k->http_bodyless && + !data->set.ignorecl && checkprefix("Content-Length:", headp)) { + curl_off_t contentlength; + CURLofft offt = curlx_strtoofft(headp + strlen("Content-Length:"), + NULL, 10, &contentlength); + + if(offt == CURL_OFFT_OK) { + k->size = contentlength; + k->maxdownload = k->size; + } + else if(offt == CURL_OFFT_FLOW) { + /* out of range */ + if(data->set.max_filesize) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; + } + streamclose(conn, "overflow content-length"); + infof(data, "Overflow Content-Length: value"); + } + else { + /* negative or just rubbish - bad HTTP */ + failf(data, "Invalid Content-Length: value"); + return CURLE_WEIRD_SERVER_REPLY; + } + } + /* check for Content-Type: header lines to get the MIME-type */ + else if(checkprefix("Content-Type:", headp)) { + char *contenttype = Curl_copy_header_value(headp); + if(!contenttype) + return CURLE_OUT_OF_MEMORY; + if(!*contenttype) + /* ignore empty data */ + free(contenttype); + else { + Curl_safefree(data->info.contenttype); + data->info.contenttype = contenttype; + } + } +#ifndef CURL_DISABLE_PROXY + else if((conn->httpversion == 10) && + conn->bits.httpproxy && + Curl_compareheader(headp, + STRCONST("Proxy-Connection:"), + STRCONST("keep-alive"))) { + /* + * When an HTTP/1.0 reply comes when using a proxy, the + * 'Proxy-Connection: keep-alive' line tells us the + * connection will be kept alive for our pleasure. + * Default action for 1.0 is to close. + */ + connkeep(conn, "Proxy-Connection keep-alive"); /* don't close */ + infof(data, "HTTP/1.0 proxy connection set to keep alive"); + } + else if((conn->httpversion == 11) && + conn->bits.httpproxy && + Curl_compareheader(headp, + STRCONST("Proxy-Connection:"), + STRCONST("close"))) { + /* + * We get an HTTP/1.1 response from a proxy and it says it'll + * close down after this transfer. + */ + connclose(conn, "Proxy-Connection: asked to close after done"); + infof(data, "HTTP/1.1 proxy connection set close"); + } +#endif + else if((conn->httpversion == 10) && + Curl_compareheader(headp, + STRCONST("Connection:"), + STRCONST("keep-alive"))) { + /* + * An HTTP/1.0 reply with the 'Connection: keep-alive' line + * tells us the connection will be kept alive for our + * pleasure. Default action for 1.0 is to close. + * + * [RFC2068, section 19.7.1] */ + connkeep(conn, "Connection keep-alive"); + infof(data, "HTTP/1.0 connection set to keep alive"); + } + else if(Curl_compareheader(headp, + STRCONST("Connection:"), STRCONST("close"))) { + /* + * [RFC 2616, section 8.1.2.1] + * "Connection: close" is HTTP/1.1 language and means that + * the connection will close when this request has been + * served. + */ + streamclose(conn, "Connection: close used"); + } + else if(!k->http_bodyless && checkprefix("Transfer-Encoding:", headp)) { + /* One or more encodings. We check for chunked and/or a compression + algorithm. */ + /* + * [RFC 2616, section 3.6.1] A 'chunked' transfer encoding + * means that the server will send a series of "chunks". Each + * chunk starts with line with info (including size of the + * coming block) (terminated with CRLF), then a block of data + * with the previously mentioned size. There can be any amount + * of chunks, and a chunk-data set to zero signals the + * end-of-chunks. */ + + result = Curl_build_unencoding_stack(data, + headp + strlen("Transfer-Encoding:"), + TRUE); + if(result) + return result; + if(!k->chunk && data->set.http_transfer_encoding) { + /* if this isn't 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"); + k->ignore_cl = TRUE; + } + } + else if(!k->http_bodyless && checkprefix("Content-Encoding:", headp) && + data->set.str[STRING_ENCODING]) { + /* + * Process Content-Encoding. Look for the values: identity, + * gzip, deflate, compress, x-gzip and x-compress. x-gzip and + * x-compress are the same as gzip and compress. (Sec 3.5 RFC + * 2616). zlib cannot handle compress. However, errors are + * handled further down when the response body is processed + */ + result = Curl_build_unencoding_stack(data, + headp + strlen("Content-Encoding:"), + FALSE); + if(result) + return result; + } + else if(checkprefix("Retry-After:", headp)) { + /* Retry-After = HTTP-date / delay-seconds */ + curl_off_t retry_after = 0; /* zero for unknown or "now" */ + /* Try it as a decimal number, if it works it is not a date */ + (void)curlx_strtoofft(headp + strlen("Retry-After:"), + NULL, 10, &retry_after); + if(!retry_after) { + time_t date = Curl_getdate_capped(headp + strlen("Retry-After:")); + if(-1 != date) + /* convert date to number of seconds into the future */ + retry_after = date - time(NULL); + } + data->info.retry_after = retry_after; /* store it */ + } + else if(!k->http_bodyless && checkprefix("Content-Range:", headp)) { + /* Content-Range: bytes [num]- + Content-Range: bytes: [num]- + Content-Range: [num]- + Content-Range: [asterisk]/[total] + + The second format was added since Sun's webserver + 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. + */ + + char *ptr = headp + strlen("Content-Range:"); + + /* Move forward until first digit or asterisk */ + while(*ptr && !ISDIGIT(*ptr) && *ptr != '*') + ptr++; + + /* if it truly stopped on a digit */ + if(ISDIGIT(*ptr)) { + if(!curlx_strtoofft(ptr, NULL, 10, &k->offset)) { + if(data->state.resume_from == k->offset) + /* we asked for a resume and we got it */ + k->content_range = TRUE; + } + } + else if(k->httpcode < 300) + data->state.resume_from = 0; /* get everything */ + } +#if !defined(CURL_DISABLE_COOKIES) + else if(data->cookies && data->state.cookie_engine && + checkprefix("Set-Cookie:", headp)) { + /* If there is a custom-set Host: name, use it here, or else use real peer + host name. */ + const char *host = data->state.aptr.cookiehost? + data->state.aptr.cookiehost:conn->host.name; + const bool secure_context = + conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) || + strcasecompare("localhost", host) || + !strcmp(host, "127.0.0.1") || + !strcmp(host, "::1") ? TRUE : FALSE; + + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, + CURL_LOCK_ACCESS_SINGLE); + Curl_cookie_add(data, data->cookies, TRUE, FALSE, + headp + strlen("Set-Cookie:"), host, + data->state.up.path, secure_context); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } +#endif + else if(!k->http_bodyless && checkprefix("Last-Modified:", headp) && + (data->set.timecondition || data->set.get_filetime) ) { + k->timeofdoc = Curl_getdate_capped(headp + strlen("Last-Modified:")); + if(data->set.get_filetime) + data->info.filetime = k->timeofdoc; + } + else if((checkprefix("WWW-Authenticate:", headp) && + (401 == k->httpcode)) || + (checkprefix("Proxy-authenticate:", headp) && + (407 == k->httpcode))) { + + bool proxy = (k->httpcode == 407) ? TRUE : FALSE; + char *auth = Curl_copy_header_value(headp); + if(!auth) + return CURLE_OUT_OF_MEMORY; + + result = Curl_http_input_auth(data, proxy, auth); + + free(auth); + + if(result) + return result; + } +#ifdef USE_SPNEGO + else if(checkprefix("Persistent-Auth:", headp)) { + struct negotiatedata *negdata = &conn->negotiate; + struct auth *authp = &data->state.authhost; + if(authp->picked == CURLAUTH_NEGOTIATE) { + char *persistentauth = Curl_copy_header_value(headp); + if(!persistentauth) + return CURLE_OUT_OF_MEMORY; + negdata->noauthpersist = checkprefix("false", persistentauth)? + TRUE:FALSE; + negdata->havenoauthpersist = TRUE; + infof(data, "Negotiate: noauthpersist -> %d, header part: %s", + negdata->noauthpersist, persistentauth); + free(persistentauth); + } + } +#endif + else if((k->httpcode >= 300 && k->httpcode < 400) && + checkprefix("Location:", headp) && + !data->req.location) { + /* this is the URL that the server advises us to use instead */ + char *location = Curl_copy_header_value(headp); + if(!location) + return CURLE_OUT_OF_MEMORY; + if(!*location) + /* ignore empty data */ + free(location); + else { + data->req.location = location; + + if(data->set.http_follow_location) { + DEBUGASSERT(!data->req.newurl); + data->req.newurl = strdup(data->req.location); /* clone */ + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + + /* some cases of POST and PUT etc needs to rewind the data + stream at this point */ + result = http_perhapsrewind(data, conn); + if(result) + return result; + + /* mark the next request as a followed location: */ + data->state.this_is_a_follow = TRUE; + } + } + } + +#ifndef CURL_DISABLE_HSTS + /* If enabled, the header is incoming and this is over HTTPS */ + else if(data->hsts && checkprefix("Strict-Transport-Security:", headp) && + ((conn->handler->flags & PROTOPT_SSL) || +#ifdef CURLDEBUG + /* allow debug builds to circumvent the HTTPS restriction */ + getenv("CURL_HSTS_HTTP") +#else + 0 +#endif + )) { + CURLcode check = + Curl_hsts_parse(data->hsts, conn->host.name, + headp + strlen("Strict-Transport-Security:")); + if(check) + infof(data, "Illegal STS header skipped"); +#ifdef DEBUGBUILD + else + infof(data, "Parsed STS header fine (%zu entries)", + data->hsts->list.size); +#endif + } +#endif +#ifndef CURL_DISABLE_ALTSVC + /* If enabled, the header is incoming and this is over HTTPS */ + else if(data->asi && checkprefix("Alt-Svc:", headp) && + ((conn->handler->flags & PROTOPT_SSL) || +#ifdef CURLDEBUG + /* allow debug builds to circumvent the HTTPS restriction */ + getenv("CURL_ALTSVC_HTTP") +#else + 0 +#endif + )) { + /* the ALPN of the current request */ + enum alpnid id = (conn->httpversion == 30)? ALPN_h3 : + (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1; + result = Curl_altsvc_parse(data, data->asi, + headp + strlen("Alt-Svc:"), + id, conn->host.name, + curlx_uitous((unsigned int)conn->remote_port)); + if(result) + return result; + } +#endif + else if(conn->handler->protocol & CURLPROTO_RTSP) { + result = Curl_rtsp_parseheader(data, headp); + if(result) + return result; + } + return CURLE_OK; +} + +/* + * Called after the first HTTP response line (the status line) has been + * received and parsed. + */ + +CURLcode Curl_http_statusline(struct Curl_easy *data, + struct connectdata *conn) +{ + struct SingleRequest *k = &data->req; + data->info.httpcode = k->httpcode; + + data->info.httpversion = conn->httpversion; + if(!data->state.httpversion || + data->state.httpversion > conn->httpversion) + /* store the lowest server version we encounter */ + data->state.httpversion = conn->httpversion; + + /* + * This code executes as part of processing the header. As a + * result, it's not totally clear how to interpret the + * response code yet as that depends on what other headers may + * be present. 401 and 407 may be errors, but may be OK + * depending on how authentication is working. Other codes + * are definitely errors, so give up here. + */ + if(data->state.resume_from && data->state.httpreq == HTTPREQ_GET && + k->httpcode == 416) { + /* "Requested Range Not Satisfiable", just proceed and + pretend this is no error */ + k->ignorebody = TRUE; /* Avoid appending error msg to good data. */ + } + + if(conn->httpversion == 10) { + /* Default action for HTTP/1.0 must be to close, unless + 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"); + } + else if(conn->httpversion == 20 || + (k->upgr101 == UPGR101_H2 && k->httpcode == 101)) { + DEBUGF(infof(data, "HTTP/2 found, allow multiplexing")); + /* HTTP/2 cannot avoid multiplexing since it is a core functionality + of the protocol */ + conn->bundle->multiuse = BUNDLE_MULTIPLEX; + } + else if(conn->httpversion >= 11 && + !conn->bits.close) { + /* If HTTP version is >= 1.1 and connection is persistent */ + DEBUGF(infof(data, + "HTTP 1.1 or later with persistent connection")); + } + + k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200; + switch(k->httpcode) { + case 304: + /* (quote from RFC2616, section 10.3.5): The 304 response + * MUST NOT contain a message-body, and thus is always + * terminated by the first empty line after the header + * fields. */ + if(data->set.timecondition) + data->info.timecond = TRUE; + /* FALLTHROUGH */ + case 204: + /* (quote from RFC2616, section 10.2.5): The server has + * fulfilled the request but does not need to return an + * entity-body ... The 204 response MUST NOT include a + * message-body, and thus is always terminated by the first + * empty line after the header fields. */ + k->size = 0; + k->maxdownload = 0; + k->http_bodyless = TRUE; + break; + default: + break; + } + return CURLE_OK; +} + +/* Content-Length must be ignored if any Transfer-Encoding is present in the + response. Refer to RFC 7230 section 3.3.3 and RFC2616 section 4.4. This is + figured out here after all headers have been received but before the final + call to the user's header callback, so that a valid content length can be + retrieved by the user in the final call. */ +CURLcode Curl_http_size(struct Curl_easy *data) +{ + struct SingleRequest *k = &data->req; + if(data->req.ignore_cl || k->chunk) { + k->size = k->maxdownload = -1; + } + else if(k->size != -1) { + if(data->set.max_filesize && + k->size > data->set.max_filesize) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; + } + Curl_pgrsSetDownloadSize(data, k->size); + k->maxdownload = k->size; + } + return CURLE_OK; +} + +static CURLcode verify_header(struct Curl_easy *data) +{ + struct SingleRequest *k = &data->req; + const char *header = Curl_dyn_ptr(&data->state.headerb); + size_t hlen = Curl_dyn_len(&data->state.headerb); + char *ptr = memchr(header, 0x00, hlen); + if(ptr) { + /* this is bad, bail out */ + failf(data, "Nul byte in header"); + return CURLE_WEIRD_SERVER_REPLY; + } + if(k->headerline < 2) + /* the first "header" is the status-line and it has no colon */ + return CURLE_OK; + if(((header[0] == ' ') || (header[0] == '\t')) && k->headerline > 2) + /* line folding, can't happen on line 2 */ + ; + else { + ptr = memchr(header, ':', hlen); + if(!ptr) { + /* this is bad, bail out */ + failf(data, "Header without colon"); + return CURLE_WEIRD_SERVER_REPLY; + } + } + return CURLE_OK; +} + +CURLcode Curl_bump_headersize(struct Curl_easy *data, + size_t delta, + bool connect_only) +{ + size_t bad = 0; + unsigned int max = MAX_HTTP_RESP_HEADER_SIZE; + if(delta < MAX_HTTP_RESP_HEADER_SIZE) { + data->info.header_size += (unsigned int)delta; + data->req.allheadercount += (unsigned int)delta; + if(!connect_only) + data->req.headerbytecount += (unsigned int)delta; + if(data->req.allheadercount > max) + bad = data->req.allheadercount; + else if(data->info.header_size > (max * 20)) { + bad = data->info.header_size; + max *= 20; + } + } + else + bad = data->req.allheadercount + delta; + if(bad) { + failf(data, "Too large response headers: %zu > %u", bad, max); + return CURLE_RECV_ERROR; + } + return CURLE_OK; +} + + +/* + * Read any HTTP header lines from the server and pass them to the client app. + */ +CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, + struct connectdata *conn, + const char *buf, size_t blen, + size_t *pconsumed) +{ + CURLcode result; + struct SingleRequest *k = &data->req; + char *headp; + char *end_ptr; + + /* header line within buffer loop */ + *pconsumed = 0; + do { + size_t line_length; + int writetype; + + /* data is in network encoding so use 0x0a instead of '\n' */ + end_ptr = memchr(buf, 0x0a, blen); + + if(!end_ptr) { + /* Not a complete header line within buffer, append the data to + the end of the headerbuff. */ + result = Curl_dyn_addn(&data->state.headerb, buf, blen); + if(result) + return result; + *pconsumed += blen; + + if(!k->headerline) { + /* check if this looks like a protocol header */ + statusline st = + checkprotoprefix(data, conn, + Curl_dyn_ptr(&data->state.headerb), + Curl_dyn_len(&data->state.headerb)); + + if(st == STATUS_BAD) { + /* this is not the beginning of a protocol first header line */ + k->header = FALSE; + k->badheader = TRUE; + streamclose(conn, "bad HTTP: No end-of-message indicator"); + if(!data->set.http09_allowed) { + failf(data, "Received HTTP/0.9 when not allowed"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + goto out; + } + } + goto out; /* read more and try again */ + } + + /* decrease the size of the remaining (supposed) header line */ + line_length = (end_ptr - buf) + 1; + result = Curl_dyn_addn(&data->state.headerb, buf, line_length); + if(result) + return result; + + blen -= line_length; + buf += line_length; + *pconsumed += line_length; + + /**** + * We now have a FULL header line in 'headerb'. + *****/ + + if(!k->headerline) { + /* the first read header */ + statusline st = checkprotoprefix(data, conn, + Curl_dyn_ptr(&data->state.headerb), + Curl_dyn_len(&data->state.headerb)); + if(st == STATUS_BAD) { + streamclose(conn, "bad HTTP: No end-of-message indicator"); + /* this is not the beginning of a protocol first header line */ + if(!data->set.http09_allowed) { + failf(data, "Received HTTP/0.9 when not allowed"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + k->header = FALSE; + if(blen) + /* since there's more, this is a partial bad header */ + k->badheader = TRUE; + else { + /* this was all we read so it's all a bad header */ + k->badheader = TRUE; + return CURLE_OK; + } + break; + } + } + + /* headers are in network encoding so use 0x0a and 0x0d instead of '\n' + and '\r' */ + headp = Curl_dyn_ptr(&data->state.headerb); + if((0x0a == *headp) || (0x0d == *headp)) { + size_t headerlen; + /* Zero-length header line means end of headers! */ + + if('\r' == *headp) + headp++; /* pass the \r byte */ + if('\n' == *headp) + headp++; /* pass the \n byte */ + + if(100 <= k->httpcode && 199 >= k->httpcode) { + /* "A user agent MAY ignore unexpected 1xx status responses." */ + switch(k->httpcode) { + case 100: + /* + * We have made an HTTP PUT or POST and this is 1.1-lingo + * that tells us that the server is OK with this and ready + * to receive the data. + * However, we'll get more headers now so we must get + * back into the header-parsing state! + */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + + /* if we did wait for this do enable write now! */ + if(k->exp100 > EXP100_SEND_DATA) { + k->exp100 = EXP100_SEND_DATA; + k->keepon |= KEEP_SEND; + Curl_expire_done(data, EXPIRE_100_TIMEOUT); + } + break; + case 101: + /* Switching Protocols */ + if(k->upgr101 == UPGR101_H2) { + /* Switching to HTTP/2 */ + DEBUGASSERT(conn->httpversion < 20); + infof(data, "Received 101, Switching to HTTP/2"); + k->upgr101 = UPGR101_RECEIVED; + + /* we'll get more headers (HTTP/2 response) */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + + /* switch to http2 now. The bytes after response headers + are also processed here, otherwise they are lost. */ + result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); + if(result) + return result; + *pconsumed += blen; + blen = 0; + } +#ifdef USE_WEBSOCKETS + else if(k->upgr101 == UPGR101_WS) { + /* verify the response */ + result = Curl_ws_accept(data, buf, blen); + if(result) + return result; + k->header = FALSE; /* no more header to parse! */ + if(data->set.connect_only) { + k->keepon &= ~KEEP_RECV; /* read no more content */ + *pconsumed += blen; + blen = 0; + } + } +#endif + else { + /* Not switching to another protocol */ + k->header = FALSE; /* no more header to parse! */ + } + break; + default: + /* the status code 1xx indicates a provisional response, so + we'll get another set of headers */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + break; + } + } + else { + if(k->upgr101 == UPGR101_H2) { + /* A requested upgrade was denied, poke the multi handle to possibly + allow a pending pipewait to continue */ + Curl_multi_connchanged(data->multi); + } + k->header = FALSE; /* no more header to parse! */ + + if((k->size == -1) && !k->chunk && !conn->bits.close && + (conn->httpversion == 11) && + !(conn->handler->protocol & CURLPROTO_RTSP) && + data->state.httpreq != HTTPREQ_HEAD) { + /* On HTTP 1.1, when connection is not to get closed, but no + Content-Length nor Transfer-Encoding chunked have been + received, according to RFC2616 section 4.4 point 5, we + assume that the server will close the connection to + signal the end of the document. */ + infof(data, "no chunk, no close, no size. Assume close to " + "signal end"); + streamclose(conn, "HTTP: No end-of-message indicator"); + } + } + + if(!k->header) { + result = Curl_http_size(data); + if(result) + return result; + } + + /* At this point we have some idea about the fate of the connection. + If we are closing the connection it may result auth failure. */ +#if defined(USE_NTLM) + if(conn->bits.close && + (((data->req.httpcode == 401) && + (conn->http_ntlm_state == NTLMSTATE_TYPE2)) || + ((data->req.httpcode == 407) && + (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) { + infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); + data->state.authproblem = TRUE; + } +#endif +#if defined(USE_SPNEGO) + if(conn->bits.close && + (((data->req.httpcode == 401) && + (conn->http_negotiate_state == GSS_AUTHRECV)) || + ((data->req.httpcode == 407) && + (conn->proxy_negotiate_state == GSS_AUTHRECV)))) { + infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); + data->state.authproblem = TRUE; + } + if((conn->http_negotiate_state == GSS_AUTHDONE) && + (data->req.httpcode != 401)) { + conn->http_negotiate_state = GSS_AUTHSUCC; + } + if((conn->proxy_negotiate_state == GSS_AUTHDONE) && + (data->req.httpcode != 407)) { + conn->proxy_negotiate_state = GSS_AUTHSUCC; + } +#endif + + /* now, only output this if the header AND body are requested: + */ + writetype = CLIENTWRITE_HEADER | + ((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0); + + headerlen = Curl_dyn_len(&data->state.headerb); + result = Curl_client_write(data, writetype, + Curl_dyn_ptr(&data->state.headerb), + headerlen); + if(result) + return result; + + result = Curl_bump_headersize(data, headerlen, FALSE); + if(result) + return result; + + /* + * When all the headers have been parsed, see if we should give + * up and return an error. + */ + if(http_should_fail(data)) { + failf(data, "The requested URL returned error: %d", + k->httpcode); + return CURLE_HTTP_RETURNED_ERROR; + } + +#ifdef USE_WEBSOCKETS + /* All non-101 HTTP status codes are bad when wanting to upgrade to + websockets */ + if(data->req.upgr101 == UPGR101_WS) { + failf(data, "Refused WebSockets upgrade: %d", k->httpcode); + return CURLE_HTTP_RETURNED_ERROR; + } +#endif + + + data->req.deductheadercount = + (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0; + + /* Curl_http_auth_act() checks what authentication methods + * that are available and decides which one (if any) to + * use. It will set 'newurl' if an auth method was picked. */ + result = Curl_http_auth_act(data); + + if(result) + return result; + + if(k->httpcode >= 300) { + if((!conn->bits.authneg) && !conn->bits.close && + !data->state.rewindbeforesend) { + /* + * General treatment of errors when about to send data. Including : + * "417 Expectation Failed", while waiting for 100-continue. + * + * The check for close above is done simply because of something + * else has already deemed the connection to get closed then + * something else should've considered the big picture and we + * avoid this check. + * + * rewindbeforesend indicates that something has told libcurl to + * continue sending even if it gets discarded + */ + + switch(data->state.httpreq) { + case HTTPREQ_PUT: + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + /* We got an error response. If this happened before the whole + * request body has been sent we stop sending and mark the + * connection for closure after we've read the entire response. + */ + Curl_expire_done(data, EXPIRE_100_TIMEOUT); + if(!k->upload_done) { + if((k->httpcode == 417) && data->state.expect100header) { + /* 417 Expectation Failed - try again without the Expect + header */ + if(!k->writebytecount && + k->exp100 == EXP100_AWAITING_CONTINUE) { + infof(data, "Got HTTP failure 417 while waiting for a 100"); + } + else { + infof(data, "Got HTTP failure 417 while sending data"); + streamclose(conn, + "Stop sending data before everything sent"); + result = http_perhapsrewind(data, conn); + if(result) + return result; + } + data->state.disableexpect = TRUE; + DEBUGASSERT(!data->req.newurl); + data->req.newurl = strdup(data->state.url); + Curl_done_sending(data, k); + } + else if(data->set.http_keep_sending_on_error) { + infof(data, "HTTP error before end of send, keep sending"); + if(k->exp100 > EXP100_SEND_DATA) { + k->exp100 = EXP100_SEND_DATA; + k->keepon |= KEEP_SEND; + } + } + else { + infof(data, "HTTP error before end of send, stop sending"); + streamclose(conn, "Stop sending data before everything sent"); + result = Curl_done_sending(data, k); + if(result) + return result; + k->upload_done = TRUE; + if(data->state.expect100header) + k->exp100 = EXP100_FAILED; + } + } + break; + + default: /* default label present to avoid compiler warnings */ + break; + } + } + + if(data->state.rewindbeforesend && + (conn->writesockfd != CURL_SOCKET_BAD)) { + /* We rewind before next send, continue sending now */ + infof(data, "Keep sending data to get tossed away"); + k->keepon |= KEEP_SEND; + } + } + + if(!k->header) { + /* + * really end-of-headers. + * + * If we requested a "no body", this is a good time to get + * out and return home. + */ + if(data->req.no_body) + k->download_done = TRUE; +#ifndef CURL_DISABLE_RTSP + else if((conn->handler->protocol & CURLPROTO_RTSP) && + (data->set.rtspreq == RTSPREQ_DESCRIBE) && + (k->size <= -1)) + /* Respect section 4.4 of rfc2326: If the Content-Length header is + absent, a length 0 must be assumed. It will prevent libcurl from + hanging on DESCRIBE request that got refused for whatever + reason */ + k->download_done = TRUE; +#endif + + /* If max download size is *zero* (nothing) we already have + nothing and can safely return ok now! But for HTTP/2, we'd + like to call http2_handle_stream_close to properly close a + stream. In order to do this, we keep reading until we + close the stream. */ + if(0 == k->maxdownload + && !Curl_conn_is_http2(data, conn, FIRSTSOCKET) + && !Curl_conn_is_http3(data, conn, FIRSTSOCKET)) + k->download_done = TRUE; + + Curl_debug(data, CURLINFO_HEADER_IN, + Curl_dyn_ptr(&data->state.headerb), + Curl_dyn_len(&data->state.headerb)); + goto out; /* exit header line loop */ + } + + /* We continue reading headers, reset the line-based header */ + Curl_dyn_reset(&data->state.headerb); + continue; + } + + /* + * Checks for special headers coming up. + */ + + writetype = CLIENTWRITE_HEADER; + if(!k->headerline++) { + /* This is the first header, it MUST be the error code line + or else we consider this to be the body right away! */ + bool fine_statusline = FALSE; + if(conn->handler->protocol & PROTO_FAMILY_HTTP) { + /* + * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 + * + * The response code is always a three-digit number in HTTP as the spec + * says. We allow any three-digit number here, but we cannot make + * guarantees on future behaviors since it isn't within the protocol. + */ + int httpversion = 0; + char *p = headp; + + while(*p && ISBLANK(*p)) + p++; + if(!strncmp(p, "HTTP/", 5)) { + p += 5; + switch(*p) { + case '1': + p++; + if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) { + if(ISBLANK(p[2])) { + httpversion = 10 + (p[1] - '0'); + p += 3; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(ISSPACE(*p)) + fine_statusline = TRUE; + } + } + } + if(!fine_statusline) { + failf(data, "Unsupported HTTP/1 subversion in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + break; + case '2': + case '3': + if(!ISBLANK(p[1])) + break; + httpversion = (*p - '0') * 10; + p += 2; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(!ISSPACE(*p)) + break; + fine_statusline = TRUE; + } + break; + default: /* unsupported */ + failf(data, "Unsupported HTTP version in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + } + + if(fine_statusline) { + if(k->httpcode < 100) { + failf(data, "Unsupported response code in HTTP response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + switch(httpversion) { + case 10: + case 11: +#ifdef USE_HTTP2 + case 20: +#endif +#ifdef ENABLE_QUIC + case 30: +#endif + conn->httpversion = (unsigned char)httpversion; + break; + default: + failf(data, "Unsupported HTTP version (%u.%d) in response", + httpversion/10, httpversion%10); + return CURLE_UNSUPPORTED_PROTOCOL; + } + + if(k->upgr101 == UPGR101_RECEIVED) { + /* supposedly upgraded to http2 now */ + if(conn->httpversion != 20) + infof(data, "Lying server, not serving HTTP/2"); + } + if(conn->httpversion < 20) { + conn->bundle->multiuse = BUNDLE_NO_MULTIUSE; + } + } + else { + /* If user has set option HTTP200ALIASES, + compare header line against list of aliases + */ + statusline check = + checkhttpprefix(data, + Curl_dyn_ptr(&data->state.headerb), + Curl_dyn_len(&data->state.headerb)); + if(check == STATUS_DONE) { + fine_statusline = TRUE; + k->httpcode = 200; + conn->httpversion = 10; + } + } + } + else if(conn->handler->protocol & CURLPROTO_RTSP) { + char *p = headp; + while(*p && ISBLANK(*p)) + p++; + if(!strncmp(p, "RTSP/", 5)) { + p += 5; + if(ISDIGIT(*p)) { + p++; + if((p[0] == '.') && ISDIGIT(p[1])) { + if(ISBLANK(p[2])) { + p += 3; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(ISSPACE(*p)) { + fine_statusline = TRUE; + conn->httpversion = 11; /* RTSP acts like HTTP 1.1 */ + } + } + } + } + } + if(!fine_statusline) + return CURLE_WEIRD_SERVER_REPLY; + } + } + + if(fine_statusline) { + result = Curl_http_statusline(data, conn); + if(result) + return result; + writetype |= CLIENTWRITE_STATUS; + } + else { + k->header = FALSE; /* this is not a header line */ + break; + } + } + + result = verify_header(data); + if(result) + return result; + + result = Curl_http_header(data, conn, headp); + if(result) + return result; + + /* + * End of header-checks. Write them to the client. + */ + if(k->httpcode/100 == 1) + writetype |= CLIENTWRITE_1XX; + + Curl_debug(data, CURLINFO_HEADER_IN, headp, + Curl_dyn_len(&data->state.headerb)); + + result = Curl_client_write(data, writetype, headp, + Curl_dyn_len(&data->state.headerb)); + if(result) + return result; + + result = Curl_bump_headersize(data, Curl_dyn_len(&data->state.headerb), + FALSE); + if(result) + return result; + + Curl_dyn_reset(&data->state.headerb); + } + while(blen); + + /* We might have reached the end of the header part here, but + there might be a non-header part left in the end of the read + buffer. */ +out: + return CURLE_OK; +} + + +/* Decode HTTP status code string. */ +CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len) +{ + CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; + int status = 0; + int i; + + if(len != 3) + goto out; + + for(i = 0; i < 3; ++i) { + char c = s[i]; + + if(c < '0' || c > '9') + goto out; + + status *= 10; + status += c - '0'; + } + result = CURLE_OK; +out: + *pstatus = result? -1 : status; + return result; +} + +CURLcode Curl_http_req_make(struct httpreq **preq, + const char *method, size_t m_len, + const char *scheme, size_t s_len, + const char *authority, size_t a_len, + const char *path, size_t p_len) +{ + struct httpreq *req; + CURLcode result = CURLE_OUT_OF_MEMORY; + + DEBUGASSERT(method); + if(m_len + 1 >= sizeof(req->method)) + return CURLE_BAD_FUNCTION_ARGUMENT; + + req = calloc(1, sizeof(*req)); + if(!req) + goto out; + memcpy(req->method, method, m_len); + if(scheme) { + req->scheme = Curl_strndup(scheme, s_len); + if(!req->scheme) + goto out; + } + if(authority) { + req->authority = Curl_strndup(authority, a_len); + if(!req->authority) + goto out; + } + if(path) { + req->path = Curl_strndup(path, p_len); + if(!req->path) + goto out; + } + Curl_dynhds_init(&req->headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&req->trailers, 0, DYN_HTTP_REQUEST); + result = CURLE_OK; + +out: + if(result && req) + Curl_http_req_free(req); + *preq = result? NULL : req; + return result; +} + +static CURLcode req_assign_url_authority(struct httpreq *req, CURLU *url) +{ + char *user, *pass, *host, *port; + struct dynbuf buf; + CURLUcode uc; + CURLcode result = CURLE_URL_MALFORMAT; + + user = pass = host = port = NULL; + Curl_dyn_init(&buf, DYN_HTTP_REQUEST); + + uc = curl_url_get(url, CURLUPART_HOST, &host, 0); + if(uc && uc != CURLUE_NO_HOST) + goto out; + if(!host) { + req->authority = NULL; + result = CURLE_OK; + goto out; + } + + uc = curl_url_get(url, CURLUPART_PORT, &port, CURLU_NO_DEFAULT_PORT); + if(uc && uc != CURLUE_NO_PORT) + goto out; + uc = curl_url_get(url, CURLUPART_USER, &user, 0); + if(uc && uc != CURLUE_NO_USER) + goto out; + if(user) { + uc = curl_url_get(url, CURLUPART_PASSWORD, &pass, 0); + if(uc && uc != CURLUE_NO_PASSWORD) + goto out; + } + + if(user) { + result = Curl_dyn_add(&buf, user); + if(result) + goto out; + if(pass) { + result = Curl_dyn_addf(&buf, ":%s", pass); + if(result) + goto out; + } + result = Curl_dyn_add(&buf, "@"); + if(result) + goto out; + } + result = Curl_dyn_add(&buf, host); + if(result) + goto out; + if(port) { + result = Curl_dyn_addf(&buf, ":%s", port); + if(result) + goto out; + } + req->authority = strdup(Curl_dyn_ptr(&buf)); + if(!req->authority) + goto out; + result = CURLE_OK; + +out: + free(user); + free(pass); + free(host); + free(port); + Curl_dyn_free(&buf); + return result; +} + +static CURLcode req_assign_url_path(struct httpreq *req, CURLU *url) +{ + char *path, *query; + struct dynbuf buf; + CURLUcode uc; + CURLcode result = CURLE_URL_MALFORMAT; + + path = query = NULL; + Curl_dyn_init(&buf, DYN_HTTP_REQUEST); + + uc = curl_url_get(url, CURLUPART_PATH, &path, CURLU_PATH_AS_IS); + if(uc) + goto out; + uc = curl_url_get(url, CURLUPART_QUERY, &query, 0); + if(uc && uc != CURLUE_NO_QUERY) + goto out; + + if(!path && !query) { + req->path = NULL; + } + else if(path && !query) { + req->path = path; + path = NULL; + } + else { + if(path) { + result = Curl_dyn_add(&buf, path); + if(result) + goto out; + } + if(query) { + result = Curl_dyn_addf(&buf, "?%s", query); + if(result) + goto out; + } + req->path = strdup(Curl_dyn_ptr(&buf)); + if(!req->path) + goto out; + } + result = CURLE_OK; + +out: + free(path); + free(query); + Curl_dyn_free(&buf); + return result; +} + +CURLcode Curl_http_req_make2(struct httpreq **preq, + const char *method, size_t m_len, + CURLU *url, const char *scheme_default) +{ + struct httpreq *req; + CURLcode result = CURLE_OUT_OF_MEMORY; + CURLUcode uc; + + DEBUGASSERT(method); + if(m_len + 1 >= sizeof(req->method)) + return CURLE_BAD_FUNCTION_ARGUMENT; + + req = calloc(1, sizeof(*req)); + if(!req) + goto out; + memcpy(req->method, method, m_len); + + uc = curl_url_get(url, CURLUPART_SCHEME, &req->scheme, 0); + if(uc && uc != CURLUE_NO_SCHEME) + goto out; + if(!req->scheme && scheme_default) { + req->scheme = strdup(scheme_default); + if(!req->scheme) + goto out; + } + + result = req_assign_url_authority(req, url); + if(result) + goto out; + result = req_assign_url_path(req, url); + if(result) + goto out; + + Curl_dynhds_init(&req->headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&req->trailers, 0, DYN_HTTP_REQUEST); + result = CURLE_OK; + +out: + if(result && req) + Curl_http_req_free(req); + *preq = result? NULL : req; + return result; +} + +void Curl_http_req_free(struct httpreq *req) +{ + if(req) { + free(req->scheme); + free(req->authority); + free(req->path); + Curl_dynhds_free(&req->headers); + Curl_dynhds_free(&req->trailers); + free(req); + } +} + +struct name_const { + const char *name; + size_t namelen; +}; + +static struct name_const H2_NON_FIELD[] = { + { STRCONST("Host") }, + { STRCONST("Upgrade") }, + { STRCONST("Connection") }, + { STRCONST("Keep-Alive") }, + { STRCONST("Proxy-Connection") }, + { STRCONST("Transfer-Encoding") }, +}; + +static bool h2_non_field(const char *name, size_t namelen) +{ + size_t i; + for(i = 0; i < sizeof(H2_NON_FIELD)/sizeof(H2_NON_FIELD[0]); ++i) { + if(namelen < H2_NON_FIELD[i].namelen) + return FALSE; + if(namelen == H2_NON_FIELD[i].namelen && + strcasecompare(H2_NON_FIELD[i].name, name)) + return TRUE; + } + return FALSE; +} + +CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers, + struct httpreq *req, struct Curl_easy *data) +{ + const char *scheme = NULL, *authority = NULL; + struct dynhds_entry *e; + size_t i; + CURLcode result; + + DEBUGASSERT(req); + DEBUGASSERT(h2_headers); + + if(req->scheme) { + scheme = req->scheme; + } + else if(strcmp("CONNECT", req->method)) { + scheme = Curl_checkheaders(data, STRCONST(HTTP_PSEUDO_SCHEME)); + if(scheme) { + scheme += sizeof(HTTP_PSEUDO_SCHEME); + while(*scheme && ISBLANK(*scheme)) + scheme++; + infof(data, "set pseudo header %s to %s", HTTP_PSEUDO_SCHEME, scheme); + } + else { + scheme = (data->conn && data->conn->handler->flags & PROTOPT_SSL)? + "https" : "http"; + } + } + + if(req->authority) { + authority = req->authority; + } + else { + e = Curl_dynhds_get(&req->headers, STRCONST("Host")); + if(e) + authority = e->value; + } + + Curl_dynhds_reset(h2_headers); + Curl_dynhds_set_opts(h2_headers, DYNHDS_OPT_LOWERCASE); + result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_METHOD), + req->method, strlen(req->method)); + if(!result && scheme) { + result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_SCHEME), + scheme, strlen(scheme)); + } + if(!result && authority) { + result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_AUTHORITY), + authority, strlen(authority)); + } + if(!result && req->path) { + result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_PATH), + req->path, strlen(req->path)); + } + for(i = 0; !result && i < Curl_dynhds_count(&req->headers); ++i) { + e = Curl_dynhds_getn(&req->headers, i); + if(!h2_non_field(e->name, e->namelen)) { + result = Curl_dynhds_add(h2_headers, e->name, e->namelen, + e->value, e->valuelen); + } + } + + return result; +} + +CURLcode Curl_http_resp_make(struct http_resp **presp, + int status, + const char *description) +{ + struct http_resp *resp; + CURLcode result = CURLE_OUT_OF_MEMORY; + + resp = calloc(1, sizeof(*resp)); + if(!resp) + goto out; + + resp->status = status; + if(description) { + resp->description = strdup(description); + if(!resp->description) + goto out; + } + Curl_dynhds_init(&resp->headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&resp->trailers, 0, DYN_HTTP_REQUEST); + result = CURLE_OK; + +out: + if(result && resp) + Curl_http_resp_free(resp); + *presp = result? NULL : resp; + return result; +} + +void Curl_http_resp_free(struct http_resp *resp) +{ + if(resp) { + free(resp->description); + Curl_dynhds_free(&resp->headers); + Curl_dynhds_free(&resp->trailers); + if(resp->prev) + Curl_http_resp_free(resp->prev); + free(resp); + } +} + +#endif /* CURL_DISABLE_HTTP */ diff --git a/Utilities/cmcurl/lib/http.h b/Utilities/cmcurl/lib/http.h new file mode 100644 index 0000000..56b0913 --- /dev/null +++ b/Utilities/cmcurl/lib/http.h @@ -0,0 +1,333 @@ +#ifndef HEADER_CURL_HTTP_H +#define HEADER_CURL_HTTP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if defined(USE_MSH3) && !defined(_WIN32) +#include <pthread.h> +#endif + +#include "bufq.h" +#include "dynhds.h" +#include "ws.h" + +typedef enum { + HTTPREQ_GET, + HTTPREQ_POST, + HTTPREQ_POST_FORM, /* we make a difference internally */ + HTTPREQ_POST_MIME, /* we make a difference internally */ + HTTPREQ_PUT, + HTTPREQ_HEAD +} Curl_HttpReq; + +#ifndef CURL_DISABLE_HTTP + +#if defined(ENABLE_QUIC) +#include <stdint.h> +#endif + +extern const struct Curl_handler Curl_handler_http; + +#ifdef USE_SSL +extern const struct Curl_handler Curl_handler_https; +#endif + +#ifdef USE_WEBSOCKETS +extern const struct Curl_handler Curl_handler_ws; + +#ifdef USE_SSL +extern const struct Curl_handler Curl_handler_wss; +#endif +#endif /* websockets */ + +struct dynhds; + +CURLcode Curl_bump_headersize(struct Curl_easy *data, + size_t delta, + bool connect_only); + +/* Header specific functions */ +bool Curl_compareheader(const char *headerline, /* line to check */ + const char *header, /* header keyword _with_ colon */ + const size_t hlen, /* len of the keyword in bytes */ + const char *content, /* content string to find */ + const size_t clen); /* len of the content in bytes */ + +char *Curl_copy_header_value(const char *header); + +char *Curl_checkProxyheaders(struct Curl_easy *data, + const struct connectdata *conn, + const char *thisheader, + const size_t thislen); +struct HTTP; /* see below */ +CURLcode Curl_buffer_send(struct dynbuf *in, + struct Curl_easy *data, + struct HTTP *http, + curl_off_t *bytes_written, + curl_off_t included_body_bytes, + int socketindex); + +CURLcode Curl_add_timecondition(struct Curl_easy *data, +#ifndef USE_HYPER + struct dynbuf *req +#else + void *headers +#endif + ); +CURLcode Curl_add_custom_headers(struct Curl_easy *data, + bool is_connect, +#ifndef USE_HYPER + struct dynbuf *req +#else + void *headers +#endif + ); +CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, + bool is_connect, + struct dynhds *hds); + +CURLcode Curl_http_compile_trailers(struct curl_slist *trailers, + struct dynbuf *buf, + struct Curl_easy *handle); + +void Curl_http_method(struct Curl_easy *data, struct connectdata *conn, + const char **method, Curl_HttpReq *); +CURLcode Curl_http_useragent(struct Curl_easy *data); +CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn); +CURLcode Curl_http_target(struct Curl_easy *data, struct connectdata *conn, + struct dynbuf *req); +CURLcode Curl_http_statusline(struct Curl_easy *data, + struct connectdata *conn); +CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, + char *headp); +CURLcode Curl_transferencode(struct Curl_easy *data); +CURLcode Curl_http_body(struct Curl_easy *data, struct connectdata *conn, + Curl_HttpReq httpreq, + const char **teep); +CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, + struct dynbuf *r, Curl_HttpReq httpreq); +bool Curl_use_http_1_1plus(const struct Curl_easy *data, + const struct connectdata *conn); +#ifndef CURL_DISABLE_COOKIES +CURLcode Curl_http_cookies(struct Curl_easy *data, + struct connectdata *conn, + struct dynbuf *r); +#else +#define Curl_http_cookies(a,b,c) CURLE_OK +#endif +CURLcode Curl_http_resume(struct Curl_easy *data, + struct connectdata *conn, + Curl_HttpReq httpreq); +CURLcode Curl_http_range(struct Curl_easy *data, + Curl_HttpReq httpreq); +CURLcode Curl_http_firstwrite(struct Curl_easy *data, + struct connectdata *conn, + bool *done); + +/* protocol-specific functions set up to be called by the main engine */ +CURLcode Curl_http(struct Curl_easy *data, bool *done); +CURLcode Curl_http_done(struct Curl_easy *data, CURLcode, bool premature); +CURLcode Curl_http_connect(struct Curl_easy *data, bool *done); + +/* These functions are in http.c */ +CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, + const char *auth); +CURLcode Curl_http_auth_act(struct Curl_easy *data); + +/* If only the PICKNONE bit is set, there has been a round-trip and we + selected to use no auth at all. Ie, we actively select no auth, as opposed + to not having one selected. The other CURLAUTH_* defines are present in the + public curl/curl.h header. */ +#define CURLAUTH_PICKNONE (1<<30) /* don't use auth */ + +/* MAX_INITIAL_POST_SIZE indicates the number of bytes that will make the POST + data get included in the initial data chunk sent to the server. If the + data is larger than this, it will automatically get split up in multiple + system calls. + + This value used to be fairly big (100K), but we must take into account that + if the server rejects the POST due for authentication reasons, this data + 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. +*/ +#ifndef MAX_INITIAL_POST_SIZE +#define MAX_INITIAL_POST_SIZE (64*1024) +#endif + +/* EXPECT_100_THRESHOLD is the request body size limit for when libcurl will + * automatically add an "Expect: 100-continue" header in HTTP requests. When + * the size is unknown, it will always add it. + * + */ +#ifndef EXPECT_100_THRESHOLD +#define EXPECT_100_THRESHOLD (1024*1024) +#endif + +/* MAX_HTTP_RESP_HEADER_SIZE is the maximum size of all response headers + combined that libcurl allows for a single HTTP response, any HTTP + version. This count includes CONNECT response headers. */ +#define MAX_HTTP_RESP_HEADER_SIZE (300*1024) + +#endif /* CURL_DISABLE_HTTP */ + +/**************************************************************************** + * HTTP unique setup + ***************************************************************************/ +struct HTTP { + curl_off_t postsize; /* off_t to handle large file sizes */ + const char *postdata; + struct back { + curl_read_callback fread_func; /* backup storage for fread pointer */ + void *fread_in; /* backup storage for fread_in pointer */ + const char *postdata; + curl_off_t postsize; + struct Curl_easy *data; + } backup; + + enum { + HTTPSEND_NADA, /* init */ + HTTPSEND_REQUEST, /* sending a request */ + HTTPSEND_BODY /* sending body */ + } sending; + +#ifndef CURL_DISABLE_HTTP + void *h2_ctx; /* HTTP/2 implementation context */ + void *h3_ctx; /* HTTP/3 implementation context */ + struct dynbuf send_buffer; /* used if the request couldn't be sent in one + chunk, points to an allocated send_buffer + struct */ +#endif +}; + +CURLcode Curl_http_size(struct Curl_easy *data); + +CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, + struct connectdata *conn, + const char *buf, size_t blen, + size_t *pconsumed); + +/** + * Curl_http_output_auth() setups the authentication headers for the + * host/proxy and the correct authentication + * method. data->state.authdone is set to TRUE when authentication is + * done. + * + * @param data all information about the current transfer + * @param conn all information about the current connection + * @param request pointer to the request keyword + * @param httpreq is the request type + * @param path pointer to the requested path + * @param proxytunnel boolean if this is the request setting up a "proxy + * tunnel" + * + * @returns CURLcode + */ +CURLcode +Curl_http_output_auth(struct Curl_easy *data, + struct connectdata *conn, + const char *request, + Curl_HttpReq httpreq, + const char *path, + bool proxytunnel); /* TRUE if this is the request setting + up the proxy tunnel */ + +/* Decode HTTP status code string. */ +CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len); + + +/** + * All about a core HTTP request, excluding body and trailers + */ +struct httpreq { + char method[24]; + char *scheme; + char *authority; + char *path; + struct dynhds headers; + struct dynhds trailers; +}; + +/** + * Create a HTTP request struct. + */ +CURLcode Curl_http_req_make(struct httpreq **preq, + const char *method, size_t m_len, + const char *scheme, size_t s_len, + const char *authority, size_t a_len, + const char *path, size_t p_len); + +CURLcode Curl_http_req_make2(struct httpreq **preq, + const char *method, size_t m_len, + CURLU *url, const char *scheme_default); + +void Curl_http_req_free(struct httpreq *req); + +#define HTTP_PSEUDO_METHOD ":method" +#define HTTP_PSEUDO_SCHEME ":scheme" +#define HTTP_PSEUDO_AUTHORITY ":authority" +#define HTTP_PSEUDO_PATH ":path" +#define HTTP_PSEUDO_STATUS ":status" + +/** + * Create the list of HTTP/2 headers which represent the request, + * using HTTP/2 pseudo headers preceding the `req->headers`. + * + * Applies the following transformations: + * - if `authority` is set, any "Host" header is removed. + * - if `authority` is unset and a "Host" header is present, use + * that as `authority` and remove "Host" + * - removes and Connection header fields as defined in rfc9113 ch. 8.2.2 + * - lower-cases the header field names + * + * @param h2_headers will contain the HTTP/2 headers on success + * @param req the request to transform + * @param data the handle to lookup defaults like ' :scheme' from + */ +CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers, + struct httpreq *req, struct Curl_easy *data); + +/** + * All about a core HTTP response, excluding body and trailers + */ +struct http_resp { + int status; + char *description; + struct dynhds headers; + struct dynhds trailers; + struct http_resp *prev; +}; + +/** + * Create a HTTP response struct. + */ +CURLcode Curl_http_resp_make(struct http_resp **presp, + int status, + const char *description); + +void Curl_http_resp_free(struct http_resp *resp); + +#endif /* HEADER_CURL_HTTP_H */ diff --git a/Utilities/cmcurl/lib/http1.c b/Utilities/cmcurl/lib/http1.c new file mode 100644 index 0000000..182234c --- /dev/null +++ b/Utilities/cmcurl/lib/http1.c @@ -0,0 +1,346 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_HTTP + +#include "urldata.h" +#include <curl/curl.h> +#include "http.h" +#include "http1.h" +#include "urlapi-int.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +#define H1_MAX_URL_LEN (8*1024) + +void Curl_h1_req_parse_init(struct h1_req_parser *parser, size_t max_line_len) +{ + memset(parser, 0, sizeof(*parser)); + parser->max_line_len = max_line_len; + Curl_dyn_init(&parser->scratch, max_line_len); +} + +void Curl_h1_req_parse_free(struct h1_req_parser *parser) +{ + if(parser) { + Curl_http_req_free(parser->req); + Curl_dyn_free(&parser->scratch); + parser->req = NULL; + parser->done = FALSE; + } +} + +static CURLcode trim_line(struct h1_req_parser *parser, int options) +{ + DEBUGASSERT(parser->line); + if(parser->line_len) { + if(parser->line[parser->line_len - 1] == '\n') + --parser->line_len; + if(parser->line_len) { + if(parser->line[parser->line_len - 1] == '\r') + --parser->line_len; + else if(options & H1_PARSE_OPT_STRICT) + return CURLE_URL_MALFORMAT; + } + else if(options & H1_PARSE_OPT_STRICT) + return CURLE_URL_MALFORMAT; + } + else if(options & H1_PARSE_OPT_STRICT) + return CURLE_URL_MALFORMAT; + + if(parser->line_len > parser->max_line_len) { + return CURLE_URL_MALFORMAT; + } + return CURLE_OK; +} + +static ssize_t detect_line(struct h1_req_parser *parser, + const char *buf, const size_t buflen, + CURLcode *err) +{ + const char *line_end; + + DEBUGASSERT(!parser->line); + line_end = memchr(buf, '\n', buflen); + if(!line_end) { + *err = CURLE_AGAIN; + return -1; + } + parser->line = buf; + parser->line_len = line_end - buf + 1; + *err = CURLE_OK; + return (ssize_t)parser->line_len; +} + +static ssize_t next_line(struct h1_req_parser *parser, + const char *buf, const size_t buflen, int options, + CURLcode *err) +{ + ssize_t nread = 0; + + if(parser->line) { + parser->line = NULL; + parser->line_len = 0; + Curl_dyn_reset(&parser->scratch); + } + + nread = detect_line(parser, buf, buflen, err); + if(nread >= 0) { + if(Curl_dyn_len(&parser->scratch)) { + /* append detected line to scratch to have the complete line */ + *err = Curl_dyn_addn(&parser->scratch, parser->line, parser->line_len); + if(*err) + return -1; + parser->line = Curl_dyn_ptr(&parser->scratch); + parser->line_len = Curl_dyn_len(&parser->scratch); + } + *err = trim_line(parser, options); + if(*err) + return -1; + } + else if(*err == CURLE_AGAIN) { + /* no line end in `buf`, add it to our scratch */ + *err = Curl_dyn_addn(&parser->scratch, (const unsigned char *)buf, buflen); + nread = (*err)? -1 : (ssize_t)buflen; + } + return nread; +} + +static CURLcode start_req(struct h1_req_parser *parser, + const char *scheme_default, int options) +{ + const char *p, *m, *target, *hv, *scheme, *authority, *path; + size_t m_len, target_len, hv_len, scheme_len, authority_len, path_len; + size_t i; + CURLU *url = NULL; + CURLcode result = CURLE_URL_MALFORMAT; /* Use this as default fail */ + + DEBUGASSERT(!parser->req); + /* line must match: "METHOD TARGET HTTP_VERSION" */ + p = memchr(parser->line, ' ', parser->line_len); + if(!p || p == parser->line) + goto out; + + m = parser->line; + m_len = p - parser->line; + target = p + 1; + target_len = hv_len = 0; + hv = NULL; + + /* URL may contain spaces so scan backwards */ + for(i = parser->line_len; i > m_len; --i) { + if(parser->line[i] == ' ') { + hv = &parser->line[i + 1]; + hv_len = parser->line_len - i; + target_len = (hv - target) - 1; + break; + } + } + /* no SPACE found or empty TARGET or empty HTTP_VERSION */ + if(!target_len || !hv_len) + goto out; + + /* TODO: we do not check HTTP_VERSION for conformity, should + + do that when STRICT option is supplied. */ + (void)hv; + + /* The TARGET can be (rfc 9112, ch. 3.2): + * origin-form: path + optional query + * absolute-form: absolute URI + * authority-form: host+port for CONNECT + * asterisk-form: '*' for OPTIONS + * + * from TARGET, we derive `scheme` `authority` `path` + * origin-form -- -- TARGET + * absolute-form URL* URL* URL* + * authority-form -- TARGET -- + * asterisk-form -- -- TARGET + */ + scheme = authority = path = NULL; + scheme_len = authority_len = path_len = 0; + + if(target_len == 1 && target[0] == '*') { + /* asterisk-form */ + path = target; + path_len = target_len; + } + else if(!strncmp("CONNECT", m, m_len)) { + /* authority-form */ + authority = target; + authority_len = target_len; + } + else if(target[0] == '/') { + /* origin-form */ + path = target; + path_len = target_len; + } + else { + /* origin-form OR absolute-form */ + CURLUcode uc; + char tmp[H1_MAX_URL_LEN]; + + /* default, unless we see an absolute URL */ + path = target; + path_len = target_len; + + /* URL parser wants 0-termination */ + if(target_len >= sizeof(tmp)) + goto out; + memcpy(tmp, target, target_len); + tmp[target_len] = '\0'; + /* See if treating TARGET as an absolute URL makes sense */ + if(Curl_is_absolute_url(tmp, NULL, 0, FALSE)) { + int url_options; + + url = curl_url(); + if(!url) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + url_options = (CURLU_NON_SUPPORT_SCHEME| + CURLU_PATH_AS_IS| + CURLU_NO_DEFAULT_PORT); + if(!(options & H1_PARSE_OPT_STRICT)) + url_options |= CURLU_ALLOW_SPACE; + uc = curl_url_set(url, CURLUPART_URL, tmp, url_options); + if(uc) { + goto out; + } + } + + if(!url && (options & H1_PARSE_OPT_STRICT)) { + /* we should have an absolute URL or have seen `/` earlier */ + goto out; + } + } + + if(url) { + result = Curl_http_req_make2(&parser->req, m, m_len, url, scheme_default); + } + else { + if(!scheme && scheme_default) { + scheme = scheme_default; + scheme_len = strlen(scheme_default); + } + result = Curl_http_req_make(&parser->req, m, m_len, scheme, scheme_len, + authority, authority_len, path, path_len); + } + +out: + curl_url_cleanup(url); + return result; +} + +ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser, + const char *buf, size_t buflen, + const char *scheme_default, int options, + CURLcode *err) +{ + ssize_t nread = 0, n; + + *err = CURLE_OK; + while(!parser->done) { + n = next_line(parser, buf, buflen, options, err); + if(n < 0) { + if(*err != CURLE_AGAIN) { + nread = -1; + } + *err = CURLE_OK; + goto out; + } + + /* Consume this line */ + nread += (size_t)n; + buf += (size_t)n; + buflen -= (size_t)n; + + if(!parser->line) { + /* consumed bytes, but line not complete */ + if(!buflen) + goto out; + } + else if(!parser->req) { + *err = start_req(parser, scheme_default, options); + if(*err) { + nread = -1; + goto out; + } + } + else if(parser->line_len == 0) { + /* last, empty line, we are finished */ + if(!parser->req) { + *err = CURLE_URL_MALFORMAT; + nread = -1; + goto out; + } + parser->done = TRUE; + Curl_dyn_reset(&parser->scratch); + /* last chance adjustments */ + } + else { + *err = Curl_dynhds_h1_add_line(&parser->req->headers, + parser->line, parser->line_len); + if(*err) { + nread = -1; + goto out; + } + } + } + +out: + return nread; +} + +CURLcode Curl_h1_req_write_head(struct httpreq *req, int http_minor, + struct dynbuf *dbuf) +{ + CURLcode result; + + result = Curl_dyn_addf(dbuf, "%s %s%s%s%s HTTP/1.%d\r\n", + req->method, + req->scheme? req->scheme : "", + req->scheme? "://" : "", + req->authority? req->authority : "", + req->path? req->path : "", + http_minor); + if(result) + goto out; + + result = Curl_dynhds_h1_dprint(&req->headers, dbuf); + if(result) + goto out; + + result = Curl_dyn_addn(dbuf, STRCONST("\r\n")); + +out: + return result; +} + +#endif /* !CURL_DISABLE_HTTP */ diff --git a/Utilities/cmcurl/lib/http1.h b/Utilities/cmcurl/lib/http1.h new file mode 100644 index 0000000..2de302f --- /dev/null +++ b/Utilities/cmcurl/lib/http1.h @@ -0,0 +1,63 @@ +#ifndef HEADER_CURL_HTTP1_H +#define HEADER_CURL_HTTP1_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_HTTP +#include "bufq.h" +#include "http.h" + +#define H1_PARSE_OPT_NONE (0) +#define H1_PARSE_OPT_STRICT (1 << 0) + +#define H1_PARSE_DEFAULT_MAX_LINE_LEN DYN_HTTP_REQUEST + +struct h1_req_parser { + struct httpreq *req; + struct dynbuf scratch; + size_t scratch_skip; + const char *line; + size_t max_line_len; + size_t line_len; + bool done; +}; + +void Curl_h1_req_parse_init(struct h1_req_parser *parser, size_t max_line_len); +void Curl_h1_req_parse_free(struct h1_req_parser *parser); + +ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser, + const char *buf, size_t buflen, + const char *scheme_default, int options, + CURLcode *err); + +CURLcode Curl_h1_req_dprint(const struct httpreq *req, + struct dynbuf *dbuf); + +CURLcode Curl_h1_req_write_head(struct httpreq *req, int http_minor, + struct dynbuf *dbuf); + +#endif /* !CURL_DISABLE_HTTP */ +#endif /* HEADER_CURL_HTTP1_H */ diff --git a/Utilities/cmcurl/lib/http2.c b/Utilities/cmcurl/lib/http2.c new file mode 100644 index 0000000..9738484 --- /dev/null +++ b/Utilities/cmcurl/lib/http2.c @@ -0,0 +1,2829 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_NGHTTP2 +#include <stdint.h> +#include <nghttp2/nghttp2.h> +#include "urldata.h" +#include "bufq.h" +#include "http1.h" +#include "http2.h" +#include "http.h" +#include "sendf.h" +#include "select.h" +#include "curl_base64.h" +#include "strcase.h" +#include "multiif.h" +#include "url.h" +#include "urlapi-int.h" +#include "cfilters.h" +#include "connect.h" +#include "rand.h" +#include "strtoofft.h" +#include "strdup.h" +#include "transfer.h" +#include "dynbuf.h" +#include "headers.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#if (NGHTTP2_VERSION_NUM < 0x010c00) +#error too old nghttp2 version, upgrade! +#endif + +#ifdef CURL_DISABLE_VERBOSE_STRINGS +#define nghttp2_session_callbacks_set_error_callback(x,y) +#endif + +#if (NGHTTP2_VERSION_NUM >= 0x010c00) +#define NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE 1 +#endif + + +/* buffer dimensioning: + * use 16K as chunk size, as that fits H2 DATA frames well */ +#define H2_CHUNK_SIZE (16 * 1024) +/* this is how much we want "in flight" for a stream */ +#define H2_STREAM_WINDOW_SIZE (10 * 1024 * 1024) +/* on receiving from TLS, we prep for holding a full stream window */ +#define H2_NW_RECV_CHUNKS (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) +/* on send into TLS, we just want to accumulate small frames */ +#define H2_NW_SEND_CHUNKS 1 +/* stream recv/send chunks are a result of window / chunk sizes */ +#define H2_STREAM_RECV_CHUNKS (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) +/* keep smaller stream upload buffer (default h2 window size) to have + * our progress bars and "upload done" reporting closer to reality */ +#define H2_STREAM_SEND_CHUNKS ((64 * 1024) / H2_CHUNK_SIZE) +/* spare chunks we keep for a full window */ +#define H2_STREAM_POOL_SPARES (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) + +/* We need to accommodate the max number of streams with their window + * sizes on the overall connection. Streams might become PAUSED which + * will block their received QUOTA in the connection window. And if we + * run out of space, the server is blocked from sending us any data. + * See #10988 for an issue with this. */ +#define HTTP2_HUGE_WINDOW_SIZE (100 * H2_STREAM_WINDOW_SIZE) + +#define H2_SETTINGS_IV_LEN 3 +#define H2_BINSETTINGS_LEN 80 + +static int populate_settings(nghttp2_settings_entry *iv, + struct Curl_easy *data) +{ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = Curl_multi_max_concurrent_streams(data->multi); + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = H2_STREAM_WINDOW_SIZE; + + iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[2].value = data->multi->push_cb != NULL; + + return 3; +} + +static ssize_t populate_binsettings(uint8_t *binsettings, + struct Curl_easy *data) +{ + nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; + int ivlen; + + ivlen = populate_settings(iv, data); + /* this returns number of bytes it wrote or a negative number on error. */ + return nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN, + iv, ivlen); +} + +struct cf_h2_ctx { + nghttp2_session *h2; + uint32_t max_concurrent_streams; + /* The easy handle used in the current filter call, cleared at return */ + struct cf_call_data call_data; + + struct bufq inbufq; /* network input */ + struct bufq outbufq; /* network output */ + struct bufc_pool stream_bufcp; /* spares for stream buffers */ + + size_t drain_total; /* sum of all stream's UrlState drain */ + int32_t goaway_error; + int32_t last_stream_id; + BIT(conn_closed); + BIT(goaway); + BIT(enable_push); + BIT(nw_out_blocked); +}; + +/* How to access `call_data` from a cf_h2 filter */ +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) \ + ((struct cf_h2_ctx *)(cf)->ctx)->call_data + +static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx) +{ + struct cf_call_data save = ctx->call_data; + + if(ctx->h2) { + nghttp2_session_del(ctx->h2); + } + Curl_bufq_free(&ctx->inbufq); + Curl_bufq_free(&ctx->outbufq); + Curl_bufcp_free(&ctx->stream_bufcp); + memset(ctx, 0, sizeof(*ctx)); + ctx->call_data = save; +} + +static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) +{ + if(ctx) { + cf_h2_ctx_clear(ctx); + free(ctx); + } +} + +static CURLcode h2_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data); + +/** + * All about the H3 internals of a stream + */ +struct stream_ctx { + /*********** for HTTP/2 we store stream-local data here *************/ + int32_t id; /* HTTP/2 protocol identifier for stream */ + struct bufq recvbuf; /* response buffer */ + struct bufq sendbuf; /* request buffer */ + struct h1_req_parser h1; /* parsing the request */ + struct dynhds resp_trailers; /* response trailer fields */ + size_t resp_hds_len; /* amount of response header bytes in recvbuf */ + size_t upload_blocked_len; + curl_off_t upload_left; /* number of request bytes left to upload */ + + char **push_headers; /* allocated array */ + size_t push_headers_used; /* number of entries filled in */ + size_t push_headers_alloc; /* number of entries allocated */ + + int status_code; /* HTTP response status code */ + uint32_t error; /* stream error code */ + uint32_t local_window_size; /* the local recv window size */ + bool resp_hds_complete; /* we have a complete, final response */ + bool closed; /* TRUE on stream close */ + bool reset; /* TRUE on stream reset */ + bool close_handled; /* TRUE if stream closure is handled by libcurl */ + bool bodystarted; + bool send_closed; /* transfer is done sending, we might have still + buffered data in stream->sendbuf to upload. */ +}; + +#define H2_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ + ((struct HTTP *)(d)->req.p.http)->h2_ctx \ + : NULL)) +#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx +#define H2_STREAM_ID(d) (H2_STREAM_CTX(d)? \ + H2_STREAM_CTX(d)->id : -2) + +/* + * Mark this transfer to get "drained". + */ +static void drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct stream_ctx *stream) +{ + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(!stream->send_closed && + (stream->upload_left || stream->upload_blocked_len)) + bits |= CURL_CSELECT_OUT; + if(data->state.dselect_bits != bits) { + CURL_TRC_CF(data, cf, "[%d] DRAIN dselect_bits=%x", + stream->id, bits); + data->state.dselect_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + +static CURLcode http2_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct stream_ctx **pstream) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream; + + (void)cf; + DEBUGASSERT(data); + if(!data->req.p.http) { + failf(data, "initialization failure, transfer not http initialized"); + return CURLE_FAILED_INIT; + } + stream = H2_STREAM_CTX(data); + if(stream) { + *pstream = stream; + return CURLE_OK; + } + + stream = calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + stream->id = -1; + Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, + H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); + Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, + H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST); + stream->resp_hds_len = 0; + stream->bodystarted = FALSE; + stream->status_code = -1; + stream->closed = FALSE; + stream->close_handled = FALSE; + stream->error = NGHTTP2_NO_ERROR; + stream->local_window_size = H2_STREAM_WINDOW_SIZE; + stream->upload_left = 0; + + H2_STREAM_LCTX(data) = stream; + *pstream = stream; + return CURLE_OK; +} + +static void http2_data_done(struct Curl_cfilter *cf, + struct Curl_easy *data, bool premature) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H2_STREAM_CTX(data); + + DEBUGASSERT(ctx); + (void)premature; + if(!stream) + return; + + if(ctx->h2) { + if(!stream->closed && stream->id > 0) { + /* RST_STREAM */ + CURL_TRC_CF(data, cf, "[%d] premature DATA_DONE, RST stream", + stream->id); + if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_STREAM_CLOSED)) + (void)nghttp2_session_send(ctx->h2); + } + if(!Curl_bufq_is_empty(&stream->recvbuf)) { + /* Anything in the recvbuf is still being counted + * in stream and connection window flow control. Need + * to free that space or the connection window might get + * exhausted eventually. */ + nghttp2_session_consume(ctx->h2, stream->id, + Curl_bufq_len(&stream->recvbuf)); + /* give WINDOW_UPATE a chance to be sent, but ignore any error */ + (void)h2_progress_egress(cf, data); + } + + /* -1 means unassigned and 0 means cleared */ + if(nghttp2_session_get_stream_user_data(ctx->h2, stream->id)) { + int rv = nghttp2_session_set_stream_user_data(ctx->h2, + stream->id, 0); + if(rv) { + infof(data, "http/2: failed to clear user_data for stream %u", + stream->id); + DEBUGASSERT(0); + } + } + } + + Curl_bufq_free(&stream->sendbuf); + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + Curl_dynhds_free(&stream->resp_trailers); + if(stream->push_headers) { + /* if they weren't used and then freed before */ + for(; stream->push_headers_used > 0; --stream->push_headers_used) { + free(stream->push_headers[stream->push_headers_used - 1]); + } + free(stream->push_headers); + stream->push_headers = NULL; + } + + free(stream); + H2_STREAM_LCTX(data) = NULL; +} + +static int h2_client_new(struct Curl_cfilter *cf, + nghttp2_session_callbacks *cbs) +{ + struct cf_h2_ctx *ctx = cf->ctx; + nghttp2_option *o; + + int rc = nghttp2_option_new(&o); + if(rc) + return rc; + /* We handle window updates ourself to enforce buffer limits */ + nghttp2_option_set_no_auto_window_update(o, 1); +#if NGHTTP2_VERSION_NUM >= 0x013200 + /* with 1.50.0 */ + /* turn off RFC 9113 leading and trailing white spaces validation against + HTTP field value. */ + nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1); +#endif + rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o); + nghttp2_option_del(o); + return rc; +} + +static ssize_t nw_in_reader(void *reader_ctx, + unsigned char *buf, size_t buflen, + CURLcode *err) +{ + struct Curl_cfilter *cf = reader_ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + return Curl_conn_cf_recv(cf->next, data, (char *)buf, buflen, err); +} + +static ssize_t nw_out_writer(void *writer_ctx, + const unsigned char *buf, size_t buflen, + CURLcode *err) +{ + struct Curl_cfilter *cf = writer_ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + if(data) { + ssize_t nwritten = Curl_conn_cf_send(cf->next, data, + (const char *)buf, buflen, err); + if(nwritten > 0) + CURL_TRC_CF(data, cf, "[0] egress: wrote %zd bytes", nwritten); + return nwritten; + } + return 0; +} + +static ssize_t send_callback(nghttp2_session *h2, + const uint8_t *mem, size_t length, int flags, + void *userp); +static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, + void *userp); +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, + void *userp); +#endif +static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const uint8_t *mem, size_t len, void *userp); +static int on_stream_close(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *userp); +static int on_begin_headers(nghttp2_session *session, + const nghttp2_frame *frame, void *userp); +static int on_header(nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, + void *userp); +static int error_callback(nghttp2_session *session, const char *msg, + size_t len, void *userp); + +/* + * Initialize the cfilter context + */ +static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool via_h1_upgrade) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream; + CURLcode result = CURLE_OUT_OF_MEMORY; + int rc; + nghttp2_session_callbacks *cbs = NULL; + + DEBUGASSERT(!ctx->h2); + Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES); + Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0); + Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); + ctx->last_stream_id = 2147483647; + + rc = nghttp2_session_callbacks_new(&cbs); + if(rc) { + failf(data, "Couldn't initialize nghttp2 callbacks"); + goto out; + } + + nghttp2_session_callbacks_set_send_callback(cbs, send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + nghttp2_session_callbacks_set_on_frame_send_callback(cbs, on_frame_send); +#endif + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + cbs, on_data_chunk_recv); + nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close); + nghttp2_session_callbacks_set_on_begin_headers_callback( + cbs, on_begin_headers); + nghttp2_session_callbacks_set_on_header_callback(cbs, on_header); + nghttp2_session_callbacks_set_error_callback(cbs, error_callback); + + /* The nghttp2 session is not yet setup, do it */ + rc = h2_client_new(cf, cbs); + if(rc) { + failf(data, "Couldn't initialize nghttp2"); + goto out; + } + ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS; + + if(via_h1_upgrade) { + /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted + * in the H1 request and we upgrade from there. This stream + * is opened implicitly as #1. */ + uint8_t binsettings[H2_BINSETTINGS_LEN]; + ssize_t binlen; /* length of the binsettings data */ + + binlen = populate_binsettings(binsettings, data); + if(binlen <= 0) { + failf(data, "nghttp2 unexpectedly failed on pack_settings_payload"); + result = CURLE_FAILED_INIT; + goto out; + } + + result = http2_data_setup(cf, data, &stream); + if(result) + goto out; + DEBUGASSERT(stream); + stream->id = 1; + /* queue SETTINGS frame (again) */ + rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen, + data->state.httpreq == HTTPREQ_HEAD, + NULL); + if(rc) { + failf(data, "nghttp2_session_upgrade2() failed: %s(%d)", + nghttp2_strerror(rc), rc); + result = CURLE_HTTP2; + goto out; + } + + rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->id, + data); + if(rc) { + infof(data, "http/2: failed to set user_data for stream %u", + stream->id); + DEBUGASSERT(0); + } + CURL_TRC_CF(data, cf, "created session via Upgrade"); + } + else { + nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; + int ivlen; + + ivlen = populate_settings(iv, data); + rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, + iv, ivlen); + if(rc) { + failf(data, "nghttp2_submit_settings() failed: %s(%d)", + nghttp2_strerror(rc), rc); + result = CURLE_HTTP2; + goto out; + } + } + + rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0, + HTTP2_HUGE_WINDOW_SIZE); + if(rc) { + failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", + nghttp2_strerror(rc), rc); + result = CURLE_HTTP2; + goto out; + } + + /* all set, traffic will be send on connect */ + result = CURLE_OK; + CURL_TRC_CF(data, cf, "[0] created h2 session%s", + via_h1_upgrade? " (via h1 upgrade)" : ""); + +out: + if(cbs) + nghttp2_session_callbacks_del(cbs); + return result; +} + +/* + * Returns nonzero if current HTTP/2 session should be closed. + */ +static int should_close_session(struct cf_h2_ctx *ctx) +{ + return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) && + !nghttp2_session_want_write(ctx->h2); +} + +/* + * Processes pending input left in network input buffer. + * This function returns 0 if it succeeds, or -1 and error code will + * be assigned to *err. + */ +static int h2_process_pending_input(struct Curl_cfilter *cf, + struct Curl_easy *data, + CURLcode *err) +{ + struct cf_h2_ctx *ctx = cf->ctx; + const unsigned char *buf; + size_t blen; + ssize_t rv; + + while(Curl_bufq_peek(&ctx->inbufq, &buf, &blen)) { + + rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen); + if(rv < 0) { + failf(data, + "process_pending_input: nghttp2_session_mem_recv() returned " + "%zd:%s", rv, nghttp2_strerror((int)rv)); + *err = CURLE_RECV_ERROR; + return -1; + } + Curl_bufq_skip(&ctx->inbufq, (size_t)rv); + if(Curl_bufq_is_empty(&ctx->inbufq)) { + break; + } + else { + CURL_TRC_CF(data, cf, "process_pending_input: %zu bytes left " + "in connection buffer", Curl_bufq_len(&ctx->inbufq)); + } + } + + if(nghttp2_session_check_request_allowed(ctx->h2) == 0) { + /* No more requests are allowed in the current session, so + 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"); + } + + return 0; +} + +/* + * The server may send us data at any point (e.g. PING frames). Therefore, + * we cannot assume that an HTTP/2 socket is dead just because it is readable. + * + * Check the lower filters first and, if successful, peek at the socket + * and distinguish between closed and data. + */ +static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data, + bool *input_pending) +{ + struct cf_h2_ctx *ctx = cf->ctx; + bool alive = TRUE; + + *input_pending = FALSE; + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) + return FALSE; + + if(*input_pending) { + /* This happens before we've sent off a request and the connection is + not in use by any other transfer, there shouldn't be any data here, + only "protocol frames" */ + CURLcode result; + ssize_t nread = -1; + + *input_pending = FALSE; + nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result); + if(nread != -1) { + CURL_TRC_CF(data, cf, "%zd bytes stray data read before trying " + "h2 connection", nread); + if(h2_process_pending_input(cf, data, &result) < 0) + /* immediate error, considered dead */ + alive = FALSE; + else { + alive = !should_close_session(ctx); + } + } + else if(result != CURLE_AGAIN) { + /* the read failed so let's say this is dead anyway */ + alive = FALSE; + } + } + + return alive; +} + +static CURLcode http2_send_ping(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + int rc; + + rc = nghttp2_submit_ping(ctx->h2, 0, ZERO_NULL); + if(rc) { + failf(data, "nghttp2_submit_ping() failed: %s(%d)", + nghttp2_strerror(rc), rc); + return CURLE_HTTP2; + } + + rc = nghttp2_session_send(ctx->h2); + if(rc) { + failf(data, "nghttp2_session_send() failed: %s(%d)", + nghttp2_strerror(rc), rc); + return CURLE_SEND_ERROR; + } + return CURLE_OK; +} + +/* + * Store nghttp2 version info in this buffer. + */ +void Curl_http2_ver(char *p, size_t len) +{ + nghttp2_info *h2 = nghttp2_version(0); + (void)msnprintf(p, len, "nghttp2/%s", h2->version_str); +} + +static CURLcode nw_out_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + ssize_t nwritten; + CURLcode result; + + (void)data; + if(Curl_bufq_is_empty(&ctx->outbufq)) + return CURLE_OK; + + nwritten = Curl_bufq_pass(&ctx->outbufq, nw_out_writer, cf, &result); + if(nwritten < 0) { + if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "flush nw send buffer(%zu) -> EAGAIN", + Curl_bufq_len(&ctx->outbufq)); + ctx->nw_out_blocked = 1; + } + return result; + } + return Curl_bufq_is_empty(&ctx->outbufq)? CURLE_OK: CURLE_AGAIN; +} + +/* + * The implementation of nghttp2_send_callback type. Here we write |data| with + * size |length| to the network and return the number of bytes actually + * written. See the documentation of nghttp2_send_callback for the details. + */ +static ssize_t send_callback(nghttp2_session *h2, + const uint8_t *buf, size_t blen, int flags, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nwritten; + CURLcode result = CURLE_OK; + + (void)h2; + (void)flags; + DEBUGASSERT(data); + + nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen, + nw_out_writer, cf, &result); + if(nwritten < 0) { + if(result == CURLE_AGAIN) { + ctx->nw_out_blocked = 1; + return NGHTTP2_ERR_WOULDBLOCK; + } + failf(data, "Failed sending HTTP2 data"); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + if(!nwritten) { + ctx->nw_out_blocked = 1; + return NGHTTP2_ERR_WOULDBLOCK; + } + return nwritten; +} + + +/* We pass a pointer to this struct in the push callback, but the contents of + the struct are hidden from the user. */ +struct curl_pushheaders { + struct Curl_easy *data; + const nghttp2_push_promise *frame; +}; + +/* + * push header access function. Only to be used from within the push callback + */ +char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) +{ + /* Verify that we got a good easy handle in the push header struct, mostly to + detect rubbish input fast(er). */ + if(!h || !GOOD_EASY_HANDLE(h->data)) + return NULL; + else { + struct stream_ctx *stream = H2_STREAM_CTX(h->data); + if(stream && num < stream->push_headers_used) + return stream->push_headers[num]; + } + return NULL; +} + +/* + * push header access function. Only to be used from within the push callback + */ +char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) +{ + struct stream_ctx *stream; + size_t len; + size_t i; + /* Verify that we got a good easy handle in the push header struct, + mostly to detect rubbish input fast(er). Also empty header name + is just a rubbish too. We have to allow ":" at the beginning of + the header, but header == ":" must be rejected. If we have ':' in + the middle of header, it could be matched in middle of the value, + this is because we do prefix match.*/ + if(!h || !GOOD_EASY_HANDLE(h->data) || !header || !header[0] || + !strcmp(header, ":") || strchr(header + 1, ':')) + return NULL; + + stream = H2_STREAM_CTX(h->data); + if(!stream) + return NULL; + + len = strlen(header); + for(i = 0; i<stream->push_headers_used; i++) { + if(!strncmp(header, stream->push_headers[i], len)) { + /* sub-match, make sure that it is followed by a colon */ + if(stream->push_headers[i][len] != ':') + continue; + return &stream->push_headers[i][len + 1]; + } + } + return NULL; +} + +static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct Curl_easy *second = curl_easy_duphandle(data); + if(second) { + /* setup the request struct */ + struct HTTP *http = calloc(1, sizeof(struct HTTP)); + if(!http) { + (void)Curl_close(&second); + } + else { + struct stream_ctx *second_stream; + + second->req.p.http = http; + http2_data_setup(cf, second, &second_stream); + second->state.priority.weight = data->state.priority.weight; + } + } + return second; +} + +static int set_transfer_url(struct Curl_easy *data, + struct curl_pushheaders *hp) +{ + const char *v; + CURLUcode uc; + char *url = NULL; + int rc = 0; + CURLU *u = curl_url(); + + if(!u) + return 5; + + v = curl_pushheader_byname(hp, HTTP_PSEUDO_SCHEME); + if(v) { + uc = curl_url_set(u, CURLUPART_SCHEME, v, 0); + if(uc) { + rc = 1; + goto fail; + } + } + + v = curl_pushheader_byname(hp, HTTP_PSEUDO_AUTHORITY); + if(v) { + uc = Curl_url_set_authority(u, v, CURLU_DISALLOW_USER); + if(uc) { + rc = 2; + goto fail; + } + } + + v = curl_pushheader_byname(hp, HTTP_PSEUDO_PATH); + if(v) { + uc = curl_url_set(u, CURLUPART_PATH, v, 0); + if(uc) { + rc = 3; + goto fail; + } + } + + uc = curl_url_get(u, CURLUPART_URL, &url, 0); + if(uc) + rc = 4; +fail: + curl_url_cleanup(u); + if(rc) + return rc; + + if(data->state.url_alloc) + free(data->state.url); + data->state.url_alloc = TRUE; + data->state.url = url; + return 0; +} + +static void discard_newhandle(struct Curl_cfilter *cf, + struct Curl_easy *newhandle) +{ + if(!newhandle->req.p.http) { + http2_data_done(cf, newhandle, TRUE); + newhandle->req.p.http = NULL; + } + (void)Curl_close(&newhandle); +} + +static int push_promise(struct Curl_cfilter *cf, + struct Curl_easy *data, + const nghttp2_push_promise *frame) +{ + struct cf_h2_ctx *ctx = cf->ctx; + int rv; /* one of the CURL_PUSH_* defines */ + + CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE received", + frame->promised_stream_id); + if(data->multi->push_cb) { + struct stream_ctx *stream; + struct stream_ctx *newstream; + struct curl_pushheaders heads; + CURLMcode rc; + CURLcode result; + size_t i; + /* clone the parent */ + struct Curl_easy *newhandle = h2_duphandle(cf, data); + if(!newhandle) { + infof(data, "failed to duplicate handle"); + rv = CURL_PUSH_DENY; /* FAIL HARD */ + goto fail; + } + + heads.data = data; + heads.frame = frame; + /* ask the application */ + CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ask application"); + + stream = H2_STREAM_CTX(data); + if(!stream) { + failf(data, "Internal NULL stream"); + discard_newhandle(cf, newhandle); + rv = CURL_PUSH_DENY; + goto fail; + } + + rv = set_transfer_url(newhandle, &heads); + if(rv) { + discard_newhandle(cf, newhandle); + rv = CURL_PUSH_DENY; + goto fail; + } + + result = http2_data_setup(cf, newhandle, &newstream); + if(result) { + failf(data, "error setting up stream: %d", result); + discard_newhandle(cf, newhandle); + rv = CURL_PUSH_DENY; + goto fail; + } + DEBUGASSERT(stream); + + 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); + + /* free the headers again */ + for(i = 0; i<stream->push_headers_used; i++) + free(stream->push_headers[i]); + free(stream->push_headers); + stream->push_headers = NULL; + stream->push_headers_used = 0; + + if(rv) { + DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT)); + /* denied, kill off the new handle again */ + discard_newhandle(cf, newhandle); + goto fail; + } + + newstream->id = frame->promised_stream_id; + newhandle->req.maxdownload = -1; + newhandle->req.size = -1; + + /* approved, add to the multi handle and immediately switch to PERFORM + state with the given connection !*/ + rc = Curl_multi_add_perform(data->multi, newhandle, cf->conn); + if(rc) { + infof(data, "failed to add handle to multi"); + discard_newhandle(cf, newhandle); + rv = CURL_PUSH_DENY; + goto fail; + } + + rv = nghttp2_session_set_stream_user_data(ctx->h2, + newstream->id, + newhandle); + if(rv) { + infof(data, "failed to set user_data for stream %u", + newstream->id); + DEBUGASSERT(0); + rv = CURL_PUSH_DENY; + goto fail; + } + } + else { + CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ignore it"); + rv = CURL_PUSH_DENY; + } +fail: + return rv; +} + +static CURLcode recvbuf_write_hds(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *buf, size_t blen) +{ + struct stream_ctx *stream = H2_STREAM_CTX(data); + ssize_t nwritten; + CURLcode result; + + (void)cf; + nwritten = Curl_bufq_write(&stream->recvbuf, + (const unsigned char *)buf, blen, &result); + if(nwritten < 0) + return result; + stream->resp_hds_len += (size_t)nwritten; + DEBUGASSERT((size_t)nwritten == blen); + return CURLE_OK; +} + +static CURLcode on_stream_frame(struct Curl_cfilter *cf, + struct Curl_easy *data, + const nghttp2_frame *frame) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H2_STREAM_CTX(data); + int32_t stream_id = frame->hd.stream_id; + CURLcode result; + size_t rbuflen; + int rv; + + if(!stream) { + CURL_TRC_CF(data, cf, "[%d] No stream_ctx set", stream_id); + return CURLE_FAILED_INIT; + } + + switch(frame->hd.type) { + case NGHTTP2_DATA: + rbuflen = Curl_bufq_len(&stream->recvbuf); + CURL_TRC_CF(data, cf, "[%d] DATA, buffered=%zu, window=%d/%d", + stream_id, rbuflen, + nghttp2_session_get_stream_effective_recv_data_length( + ctx->h2, stream->id), + nghttp2_session_get_stream_effective_local_window_size( + ctx->h2, stream->id)); + /* If !body started on this stream, then receiving DATA is illegal. */ + if(!stream->bodystarted) { + rv = nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream_id, NGHTTP2_PROTOCOL_ERROR); + + if(nghttp2_is_fatal(rv)) { + return CURLE_RECV_ERROR; + } + } + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + drain_stream(cf, data, stream); + } + else if(rbuflen > stream->local_window_size) { + int32_t wsize = nghttp2_session_get_stream_local_window_size( + ctx->h2, stream->id); + if(wsize > 0 && (uint32_t)wsize != stream->local_window_size) { + /* H2 flow control is not absolute, as the server might not have the + * same view, yet. When we receive more than we want, we enforce + * the local window size again to make nghttp2 send WINDOW_UPATEs + * accordingly. */ + nghttp2_session_set_local_window_size(ctx->h2, + NGHTTP2_FLAG_NONE, + stream->id, + stream->local_window_size); + } + } + break; + case NGHTTP2_HEADERS: + if(stream->bodystarted) { + /* Only valid HEADERS after body started is trailer HEADERS. We + buffer them in on_header callback. */ + break; + } + + /* nghttp2 guarantees that :status is received, and we store it to + stream->status_code. Fuzzing has proven this can still be reached + without status code having been set. */ + if(stream->status_code == -1) + return CURLE_RECV_ERROR; + + /* Only final status code signals the end of header */ + if(stream->status_code / 100 != 1) { + stream->bodystarted = TRUE; + stream->status_code = -1; + } + + result = recvbuf_write_hds(cf, data, STRCONST("\r\n")); + if(result) + return result; + + if(stream->status_code / 100 != 1) { + stream->resp_hds_complete = TRUE; + } + drain_stream(cf, data, stream); + break; + case NGHTTP2_PUSH_PROMISE: + rv = push_promise(cf, data, &frame->push_promise); + if(rv) { /* deny! */ + DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT)); + rv = nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + frame->push_promise.promised_stream_id, + NGHTTP2_CANCEL); + if(nghttp2_is_fatal(rv)) + return CURLE_SEND_ERROR; + else if(rv == CURL_PUSH_ERROROUT) { + CURL_TRC_CF(data, cf, "[%d] fail in PUSH_PROMISE received", + stream_id); + return CURLE_RECV_ERROR; + } + } + break; + case NGHTTP2_RST_STREAM: + stream->closed = TRUE; + if(frame->rst_stream.error_code) { + stream->reset = TRUE; + } + stream->send_closed = TRUE; + drain_stream(cf, data, stream); + break; + case NGHTTP2_WINDOW_UPDATE: + if(CURL_WANT_SEND(data)) { + drain_stream(cf, data, stream); + } + break; + default: + break; + } + return CURLE_OK; +} + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static int fr_print(const nghttp2_frame *frame, char *buffer, size_t blen) +{ + switch(frame->hd.type) { + case NGHTTP2_DATA: { + return msnprintf(buffer, blen, + "FRAME[DATA, len=%d, eos=%d, padlen=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM), + (int)frame->data.padlen); + } + case NGHTTP2_HEADERS: { + return msnprintf(buffer, blen, + "FRAME[HEADERS, len=%d, hend=%d, eos=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS), + !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)); + } + case NGHTTP2_PRIORITY: { + return msnprintf(buffer, blen, + "FRAME[PRIORITY, len=%d, flags=%d]", + (int)frame->hd.length, frame->hd.flags); + } + case NGHTTP2_RST_STREAM: { + return msnprintf(buffer, blen, + "FRAME[RST_STREAM, len=%d, flags=%d, error=%u]", + (int)frame->hd.length, frame->hd.flags, + frame->rst_stream.error_code); + } + case NGHTTP2_SETTINGS: { + if(frame->hd.flags & NGHTTP2_FLAG_ACK) { + return msnprintf(buffer, blen, "FRAME[SETTINGS, ack=1]"); + } + return msnprintf(buffer, blen, + "FRAME[SETTINGS, len=%d]", (int)frame->hd.length); + } + case NGHTTP2_PUSH_PROMISE: { + return msnprintf(buffer, blen, + "FRAME[PUSH_PROMISE, len=%d, hend=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS)); + } + case NGHTTP2_PING: { + return msnprintf(buffer, blen, + "FRAME[PING, len=%d, ack=%d]", + (int)frame->hd.length, + frame->hd.flags&NGHTTP2_FLAG_ACK); + } + case NGHTTP2_GOAWAY: { + char scratch[128]; + size_t s_len = sizeof(scratch)/sizeof(scratch[0]); + size_t len = (frame->goaway.opaque_data_len < s_len)? + frame->goaway.opaque_data_len : s_len-1; + if(len) + memcpy(scratch, frame->goaway.opaque_data, len); + scratch[len] = '\0'; + return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', " + "last_stream=%d]", frame->goaway.error_code, + scratch, frame->goaway.last_stream_id); + } + case NGHTTP2_WINDOW_UPDATE: { + return msnprintf(buffer, blen, + "FRAME[WINDOW_UPDATE, incr=%d]", + frame->window_update.window_size_increment); + } + default: + return msnprintf(buffer, blen, "FRAME[%d, len=%d, flags=%d]", + frame->hd.type, (int)frame->hd.length, + frame->hd.flags); + } +} + +static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + (void)session; + DEBUGASSERT(data); + if(data && Curl_trc_cf_is_verbose(cf, data)) { + char buffer[256]; + int len; + len = fr_print(frame, buffer, sizeof(buffer)-1); + buffer[len] = 0; + CURL_TRC_CF(data, cf, "[%d] -> %s", frame->hd.stream_id, buffer); + } + return 0; +} +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + +static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf), *data_s; + int32_t stream_id = frame->hd.stream_id; + + DEBUGASSERT(data); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(Curl_trc_cf_is_verbose(cf, data)) { + char buffer[256]; + int len; + len = fr_print(frame, buffer, sizeof(buffer)-1); + buffer[len] = 0; + CURL_TRC_CF(data, cf, "[%d] <- %s",frame->hd.stream_id, buffer); + } +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + + if(!stream_id) { + /* stream ID zero is for connection-oriented stuff */ + DEBUGASSERT(data); + switch(frame->hd.type) { + case NGHTTP2_SETTINGS: { + if(!(frame->hd.flags & NGHTTP2_FLAG_ACK)) { + uint32_t max_conn = ctx->max_concurrent_streams; + ctx->max_concurrent_streams = nghttp2_session_get_remote_settings( + session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); + ctx->enable_push = nghttp2_session_get_remote_settings( + session, NGHTTP2_SETTINGS_ENABLE_PUSH) != 0; + CURL_TRC_CF(data, cf, "[0] MAX_CONCURRENT_STREAMS: %d", + ctx->max_concurrent_streams); + CURL_TRC_CF(data, cf, "[0] ENABLE_PUSH: %s", + ctx->enable_push ? "TRUE" : "false"); + if(data && max_conn != ctx->max_concurrent_streams) { + /* only signal change if the value actually changed */ + CURL_TRC_CF(data, cf, "[0] notify MAX_CONCURRENT_STREAMS: %u", + ctx->max_concurrent_streams); + Curl_multi_connchanged(data->multi); + } + /* Since the initial stream window is 64K, a request might be on HOLD, + * due to exhaustion. The (initial) SETTINGS may announce a much larger + * window and *assume* that we treat this like a WINDOW_UPDATE. Some + * servers send an explicit WINDOW_UPDATE, but not all seem to do that. + * To be safe, we UNHOLD a stream in order not to stall. */ + if(CURL_WANT_SEND(data)) { + struct stream_ctx *stream = H2_STREAM_CTX(data); + if(stream) + drain_stream(cf, data, stream); + } + } + break; + } + case NGHTTP2_GOAWAY: + ctx->goaway = TRUE; + ctx->goaway_error = frame->goaway.error_code; + ctx->last_stream_id = frame->goaway.last_stream_id; + if(data) { + infof(data, "received GOAWAY, error=%d, last_stream=%u", + ctx->goaway_error, ctx->last_stream_id); + Curl_multi_connchanged(data->multi); + } + break; + default: + break; + } + return 0; + } + + data_s = nghttp2_session_get_stream_user_data(session, stream_id); + if(!data_s) { + CURL_TRC_CF(data, cf, "[%d] No Curl_easy associated", stream_id); + return 0; + } + + return on_stream_frame(cf, data_s, frame)? NGHTTP2_ERR_CALLBACK_FAILURE : 0; +} + +static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const uint8_t *mem, size_t len, void *userp) +{ + struct Curl_cfilter *cf = userp; + struct stream_ctx *stream; + struct Curl_easy *data_s; + ssize_t nwritten; + CURLcode result; + (void)flags; + + DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ + DEBUGASSERT(CF_DATA_CURRENT(cf)); + + /* get the stream from the hash based on Stream ID */ + data_s = nghttp2_session_get_stream_user_data(session, stream_id); + if(!data_s) { + /* Receiving a Stream ID not in the hash should not happen - unless + we have aborted a transfer artificially and there were more data + in the pipeline. Silently ignore. */ + CURL_TRC_CF(CF_DATA_CURRENT(cf), cf, "[%d] Data for unknown", + stream_id); + /* consumed explicitly as no one will read it */ + nghttp2_session_consume(session, stream_id, len); + return 0; + } + + stream = H2_STREAM_CTX(data_s); + if(!stream) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + nwritten = Curl_bufq_write(&stream->recvbuf, mem, len, &result); + if(nwritten < 0) { + if(result != CURLE_AGAIN) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + nwritten = 0; + } + + /* if we receive data for another handle, wake that up */ + drain_stream(cf, data_s, stream); + + DEBUGASSERT((size_t)nwritten == len); + return 0; +} + +static int on_stream_close(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *userp) +{ + struct Curl_cfilter *cf = userp; + struct Curl_easy *data_s; + struct stream_ctx *stream; + int rv; + (void)session; + + /* get the stream from the hash based on Stream ID, stream ID zero is for + connection-oriented stuff */ + data_s = stream_id? + nghttp2_session_get_stream_user_data(session, stream_id) : NULL; + if(!data_s) { + return 0; + } + stream = H2_STREAM_CTX(data_s); + if(!stream) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + stream->closed = TRUE; + stream->error = error_code; + if(stream->error) + stream->reset = TRUE; + + if(stream->error) + CURL_TRC_CF(data_s, cf, "[%d] RESET: %s (err %d)", + stream_id, nghttp2_http2_strerror(error_code), error_code); + else + CURL_TRC_CF(data_s, cf, "[%d] CLOSED", stream_id); + drain_stream(cf, data_s, stream); + + /* remove `data_s` from the nghttp2 stream */ + rv = nghttp2_session_set_stream_user_data(session, stream_id, 0); + if(rv) { + infof(data_s, "http/2: failed to clear user_data for stream %u", + stream_id); + DEBUGASSERT(0); + } + return 0; +} + +static int on_begin_headers(nghttp2_session *session, + const nghttp2_frame *frame, void *userp) +{ + struct Curl_cfilter *cf = userp; + struct stream_ctx *stream; + struct Curl_easy *data_s = NULL; + + (void)cf; + data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + if(!data_s) { + return 0; + } + + if(frame->hd.type != NGHTTP2_HEADERS) { + return 0; + } + + stream = H2_STREAM_CTX(data_s); + if(!stream || !stream->bodystarted) { + return 0; + } + + return 0; +} + +/* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */ +static int on_header(nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct stream_ctx *stream; + struct Curl_easy *data_s; + int32_t stream_id = frame->hd.stream_id; + CURLcode result; + (void)flags; + + DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ + + /* get the stream from the hash based on Stream ID */ + data_s = nghttp2_session_get_stream_user_data(session, stream_id); + if(!data_s) + /* Receiving a Stream ID not in the hash should not happen, this is an + internal error more than anything else! */ + return NGHTTP2_ERR_CALLBACK_FAILURE; + + stream = H2_STREAM_CTX(data_s); + if(!stream) { + failf(data_s, "Internal NULL stream"); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + /* Store received PUSH_PROMISE headers to be used when the subsequent + PUSH_PROMISE callback comes */ + if(frame->hd.type == NGHTTP2_PUSH_PROMISE) { + char *h; + + if(!strcmp(HTTP_PSEUDO_AUTHORITY, (const char *)name)) { + /* pseudo headers are lower case */ + int rc = 0; + char *check = aprintf("%s:%d", cf->conn->host.name, + cf->conn->remote_port); + if(!check) + /* no memory */ + return NGHTTP2_ERR_CALLBACK_FAILURE; + if(!strcasecompare(check, (const char *)value) && + ((cf->conn->remote_port != cf->conn->given->defport) || + !strcasecompare(cf->conn->host.name, (const char *)value))) { + /* This is push is not for the same authority that was asked for in + * the URL. RFC 7540 section 8.2 says: "A client MUST treat a + * PUSH_PROMISE for which the server is not authoritative as a stream + * error of type PROTOCOL_ERROR." + */ + (void)nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, + stream_id, NGHTTP2_PROTOCOL_ERROR); + rc = NGHTTP2_ERR_CALLBACK_FAILURE; + } + free(check); + if(rc) + return rc; + } + + if(!stream->push_headers) { + stream->push_headers_alloc = 10; + stream->push_headers = malloc(stream->push_headers_alloc * + sizeof(char *)); + if(!stream->push_headers) + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + stream->push_headers_used = 0; + } + else if(stream->push_headers_used == + stream->push_headers_alloc) { + char **headp; + if(stream->push_headers_alloc > 1000) { + /* this is beyond crazy many headers, bail out */ + failf(data_s, "Too many PUSH_PROMISE headers"); + Curl_safefree(stream->push_headers); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + stream->push_headers_alloc *= 2; + headp = Curl_saferealloc(stream->push_headers, + stream->push_headers_alloc * sizeof(char *)); + if(!headp) { + stream->push_headers = NULL; + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + stream->push_headers = headp; + } + h = aprintf("%s:%s", name, value); + if(h) + stream->push_headers[stream->push_headers_used++] = h; + return 0; + } + + if(stream->bodystarted) { + /* This is a trailer */ + CURL_TRC_CF(data_s, cf, "[%d] trailer: %.*s: %.*s", + stream->id, (int)namelen, name, (int)valuelen, value); + result = Curl_dynhds_add(&stream->resp_trailers, + (const char *)name, namelen, + (const char *)value, valuelen); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + return 0; + } + + if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 && + memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) { + /* nghttp2 guarantees :status is received first and only once. */ + char buffer[32]; + result = Curl_http_decode_status(&stream->status_code, + (const char *)value, valuelen); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + msnprintf(buffer, sizeof(buffer), HTTP_PSEUDO_STATUS ":%u\r", + stream->status_code); + result = Curl_headers_push(data_s, buffer, CURLH_PSEUDO); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + result = recvbuf_write_hds(cf, data_s, STRCONST("HTTP/2 ")); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + /* the space character after the status code is mandatory */ + result = recvbuf_write_hds(cf, data_s, STRCONST(" \r\n")); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + /* if we receive data for another handle, wake that up */ + if(CF_DATA_CURRENT(cf) != data_s) + Curl_expire(data_s, 0, EXPIRE_RUN_NOW); + + CURL_TRC_CF(data_s, cf, "[%d] status: HTTP/2 %03d", + stream->id, stream->status_code); + return 0; + } + + /* nghttp2 guarantees that namelen > 0, and :status was already + received, and this is not pseudo-header field . */ + /* convert to an HTTP1-style header */ + result = recvbuf_write_hds(cf, data_s, (const char *)name, namelen); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + result = recvbuf_write_hds(cf, data_s, STRCONST(": ")); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + result = recvbuf_write_hds(cf, data_s, STRCONST("\r\n")); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; + /* if we receive data for another handle, wake that up */ + if(CF_DATA_CURRENT(cf) != data_s) + Curl_expire(data_s, 0, EXPIRE_RUN_NOW); + + CURL_TRC_CF(data_s, cf, "[%d] header: %.*s: %.*s", + stream->id, (int)namelen, name, (int)valuelen, value); + + return 0; /* 0 is successful */ +} + +static ssize_t req_body_read_callback(nghttp2_session *session, + int32_t stream_id, + uint8_t *buf, size_t length, + uint32_t *data_flags, + nghttp2_data_source *source, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct Curl_easy *data_s; + struct stream_ctx *stream = NULL; + CURLcode result; + ssize_t nread; + (void)source; + + (void)cf; + if(stream_id) { + /* get the stream from the hash based on Stream ID, stream ID zero is for + connection-oriented stuff */ + data_s = nghttp2_session_get_stream_user_data(session, stream_id); + if(!data_s) + /* Receiving a Stream ID not in the hash should not happen, this is an + internal error more than anything else! */ + return NGHTTP2_ERR_CALLBACK_FAILURE; + + stream = H2_STREAM_CTX(data_s); + if(!stream) + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + else + return NGHTTP2_ERR_INVALID_ARGUMENT; + + nread = Curl_bufq_read(&stream->sendbuf, buf, length, &result); + if(nread < 0) { + if(result != CURLE_AGAIN) + return NGHTTP2_ERR_CALLBACK_FAILURE; + nread = 0; + } + + if(nread > 0 && stream->upload_left != -1) + stream->upload_left -= nread; + + CURL_TRC_CF(data_s, cf, "[%d] req_body_read(len=%zu) left=%" + CURL_FORMAT_CURL_OFF_T " -> %zd, %d", + stream_id, length, stream->upload_left, nread, result); + + if(stream->upload_left == 0) + *data_flags = NGHTTP2_DATA_FLAG_EOF; + else if(nread == 0) + return NGHTTP2_ERR_DEFERRED; + + return nread; +} + +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) +static int error_callback(nghttp2_session *session, + const char *msg, + size_t len, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + (void)session; + failf(data, "%.*s", (int)len, msg); + return 0; +} +#endif + +/* + * Append headers to ask for an HTTP1.1 to HTTP2 upgrade. + */ +CURLcode Curl_http2_request_upgrade(struct dynbuf *req, + struct Curl_easy *data) +{ + CURLcode result; + char *base64; + size_t blen; + struct SingleRequest *k = &data->req; + uint8_t binsettings[H2_BINSETTINGS_LEN]; + ssize_t binlen; /* length of the binsettings data */ + + binlen = populate_binsettings(binsettings, data); + if(binlen <= 0) { + failf(data, "nghttp2 unexpectedly failed on pack_settings_payload"); + Curl_dyn_free(req); + return CURLE_FAILED_INIT; + } + + result = Curl_base64url_encode((const char *)binsettings, binlen, + &base64, &blen); + if(result) { + Curl_dyn_free(req); + return result; + } + + result = Curl_dyn_addf(req, + "Connection: Upgrade, HTTP2-Settings\r\n" + "Upgrade: %s\r\n" + "HTTP2-Settings: %s\r\n", + NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, base64); + free(base64); + + k->upgr101 = UPGR101_H2; + + return result; +} + +static CURLcode http2_data_done_send(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct stream_ctx *stream = H2_STREAM_CTX(data); + + if(!ctx || !ctx->h2 || !stream) + goto out; + + CURL_TRC_CF(data, cf, "[%d] data done send", stream->id); + if(!stream->send_closed) { + stream->send_closed = TRUE; + if(stream->upload_left) { + /* we now know that everything that is buffered is all there is. */ + stream->upload_left = Curl_bufq_len(&stream->sendbuf); + /* resume sending here to trigger the callback to get called again so + that it can signal EOF to nghttp2 */ + (void)nghttp2_session_resume_data(ctx->h2, stream->id); + drain_stream(cf, data, stream); + } + } + +out: + return result; +} + +static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct stream_ctx *stream, + CURLcode *err) +{ + ssize_t rv = 0; + + if(stream->error == NGHTTP2_REFUSED_STREAM) { + CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new " + "connection", stream->id); + connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */ + data->state.refused_stream = TRUE; + *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ + return -1; + } + else if(stream->error != NGHTTP2_NO_ERROR) { + failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)", + stream->id, nghttp2_http2_strerror(stream->error), + stream->error); + *err = CURLE_HTTP2_STREAM; + return -1; + } + else if(stream->reset) { + failf(data, "HTTP/2 stream %u was reset", stream->id); + *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; + return -1; + } + + if(!stream->bodystarted) { + failf(data, "HTTP/2 stream %u was closed cleanly, but before getting " + " all response header fields, treated as error", + stream->id); + *err = CURLE_HTTP2_STREAM; + return -1; + } + + if(Curl_dynhds_count(&stream->resp_trailers)) { + struct dynhds_entry *e; + struct dynbuf dbuf; + size_t i; + + *err = CURLE_OK; + Curl_dyn_init(&dbuf, DYN_TRAILERS); + for(i = 0; i < Curl_dynhds_count(&stream->resp_trailers); ++i) { + e = Curl_dynhds_getn(&stream->resp_trailers, i); + if(!e) + break; + Curl_dyn_reset(&dbuf); + *err = Curl_dyn_addf(&dbuf, "%.*s: %.*s\x0d\x0a", + (int)e->namelen, e->name, + (int)e->valuelen, e->value); + if(*err) + break; + Curl_debug(data, CURLINFO_HEADER_IN, Curl_dyn_ptr(&dbuf), + Curl_dyn_len(&dbuf)); + *err = Curl_client_write(data, CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER, + Curl_dyn_ptr(&dbuf), Curl_dyn_len(&dbuf)); + if(*err) + break; + } + Curl_dyn_free(&dbuf); + if(*err) + goto out; + } + + stream->close_handled = TRUE; + *err = CURLE_OK; + rv = 0; + +out: + CURL_TRC_CF(data, cf, "handle_stream_close -> %zd, %d", rv, *err); + return rv; +} + +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; +} + +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; +} + +/* + * h2_pri_spec() fills in the pri_spec struct, used by nghttp2 to send weight + * and dependency to the peer. It also stores the updated values in the state + * struct. + */ + +static void h2_pri_spec(struct Curl_easy *data, + nghttp2_priority_spec *pri_spec) +{ + struct Curl_data_priority *prio = &data->set.priority; + struct stream_ctx *depstream = H2_STREAM_CTX(prio->parent); + int32_t depstream_id = depstream? depstream->id:0; + nghttp2_priority_spec_init(pri_spec, depstream_id, + sweight_wanted(data), + data->set.priority.exclusive); + data->state.priority = *prio; +} + +/* + * Check if there's been an update in the priority / + * dependency settings and if so it submits a PRIORITY frame with the updated + * info. + * Flush any out data pending in the network buffer. + */ +static CURLcode h2_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H2_STREAM_CTX(data); + int rv = 0; + + if(stream && stream->id > 0 && + ((sweight_wanted(data) != sweight_in_effect(data)) || + (data->set.priority.exclusive != data->state.priority.exclusive) || + (data->set.priority.parent != data->state.priority.parent)) ) { + /* send new weight and/or dependency */ + nghttp2_priority_spec pri_spec; + + h2_pri_spec(data, &pri_spec); + CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id); + DEBUGASSERT(stream->id != -1); + rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, &pri_spec); + if(rv) + goto out; + } + + ctx->nw_out_blocked = 0; + while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2)) + rv = nghttp2_session_send(ctx->h2); + +out: + if(nghttp2_is_fatal(rv)) { + CURL_TRC_CF(data, cf, "nghttp2_session_send error (%s)%d", + nghttp2_strerror(rv), rv); + return CURLE_SEND_ERROR; + } + return nw_out_flush(cf, data); +} + +static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + struct stream_ctx *stream, + char *buf, size_t len, CURLcode *err) +{ + struct cf_h2_ctx *ctx = cf->ctx; + ssize_t nread = -1; + + *err = CURLE_AGAIN; + if(!Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + if(nread < 0) + goto out; + DEBUGASSERT(nread > 0); + } + + if(nread < 0) { + if(stream->closed) { + CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id); + nread = http2_handle_stream_close(cf, data, stream, err); + } + else if(stream->reset || + (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || + (ctx->goaway && ctx->last_stream_id < stream->id)) { + CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id); + *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; + nread = -1; + } + } + else if(nread == 0) { + *err = CURLE_AGAIN; + nread = -1; + } + +out: + if(nread < 0 && *err != CURLE_AGAIN) + CURL_TRC_CF(data, cf, "[%d] stream_recv(len=%zu) -> %zd, %d", + stream->id, len, nread, *err); + return nread; +} + +static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream; + CURLcode result = CURLE_OK; + ssize_t nread; + + /* Process network input buffer fist */ + if(!Curl_bufq_is_empty(&ctx->inbufq)) { + CURL_TRC_CF(data, cf, "Process %zu bytes in connection buffer", + Curl_bufq_len(&ctx->inbufq)); + if(h2_process_pending_input(cf, data, &result) < 0) + return result; + } + + /* Receive data from the "lower" filters, e.g. network until + * it is time to stop due to connection close or us not processing + * all network input */ + while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { + stream = H2_STREAM_CTX(data); + if(stream && (stream->closed || Curl_bufq_is_full(&stream->recvbuf))) { + /* We would like to abort here and stop processing, so that + * the transfer loop can handle the data/close here. However, + * this may leave data in underlying buffers that will not + * be consumed. */ + if(!cf->next || !cf->next->cft->has_data_pending(cf->next, data)) + break; + } + + nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result); + if(nread < 0) { + if(result != CURLE_AGAIN) { + failf(data, "Failed receiving HTTP2 data: %d(%s)", result, + curl_easy_strerror(result)); + return result; + } + break; + } + else if(nread == 0) { + CURL_TRC_CF(data, cf, "[0] ingress: connection closed"); + ctx->conn_closed = TRUE; + break; + } + else { + CURL_TRC_CF(data, cf, "[0] ingress: read %zd bytes", + nread); + } + + if(h2_process_pending_input(cf, data, &result)) + return result; + } + + if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { + connclose(cf->conn, "GOAWAY received"); + } + + return CURLE_OK; +} + +static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H2_STREAM_CTX(data); + ssize_t nread = -1; + CURLcode result; + struct cf_call_data save; + + if(!stream) { + /* Abnormal call sequence: either this transfer has never opened a stream + * (unlikely) or the transfer has been done, cleaned up its resources, but + * a read() is called anyway. It is not clear what the calling sequence + * is for such a case. */ + failf(data, "[%zd-%zd], http/2 recv on a transfer never opened " + "or already cleared", (ssize_t)data->id, + (ssize_t)cf->conn->connection_id); + *err = CURLE_HTTP2; + return -1; + } + + CF_DATA_SAVE(save, cf, data); + + nread = stream_recv(cf, data, stream, buf, len, err); + if(nread < 0 && *err != CURLE_AGAIN) + goto out; + + if(nread < 0) { + *err = h2_progress_ingress(cf, data); + if(*err) + goto out; + + nread = stream_recv(cf, data, stream, buf, len, err); + } + + if(nread > 0) { + size_t data_consumed = (size_t)nread; + /* Now that we transferred this to the upper layer, we report + * the actual amount of DATA consumed to the H2 session, so + * that it adjusts stream flow control */ + if(stream->resp_hds_len >= data_consumed) { + stream->resp_hds_len -= data_consumed; /* no DATA */ + } + else { + if(stream->resp_hds_len) { + data_consumed -= stream->resp_hds_len; + stream->resp_hds_len = 0; + } + if(data_consumed) { + nghttp2_session_consume(ctx->h2, stream->id, data_consumed); + } + } + + if(stream->closed) { + CURL_TRC_CF(data, cf, "[%d] DRAIN closed stream", stream->id); + drain_stream(cf, data, stream); + } + } + +out: + result = h2_progress_egress(cf, data); + if(result == CURLE_AGAIN) { + /* pending data to send, need to be called again. Ideally, we'd + * monitor the socket for POLLOUT, but we might not be in SENDING + * transfer state any longer and are unable to make this happen. + */ + drain_stream(cf, data, stream); + } + else if(result) { + *err = result; + nread = -1; + } + CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d, " + "buffered=%zu, window=%d/%d, connection %d/%d", + stream->id, len, nread, *err, + Curl_bufq_len(&stream->recvbuf), + nghttp2_session_get_stream_effective_recv_data_length( + ctx->h2, stream->id), + nghttp2_session_get_stream_effective_local_window_size( + ctx->h2, stream->id), + nghttp2_session_get_local_window_size(ctx->h2), + HTTP2_HUGE_WINDOW_SIZE); + + CF_DATA_RESTORE(cf, save); + return nread; +} + +static ssize_t h2_submit(struct stream_ctx **pstream, + struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream = NULL; + struct dynhds h2_headers; + nghttp2_nv *nva = NULL; + const void *body = NULL; + size_t nheader, bodylen, i; + nghttp2_data_provider data_prd; + int32_t stream_id; + nghttp2_priority_spec pri_spec; + ssize_t nwritten; + + Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + + *err = http2_data_setup(cf, data, &stream); + if(*err) { + nwritten = -1; + goto out; + } + + nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); + if(nwritten < 0) + goto out; + if(!stream->h1.done) { + /* need more data */ + goto out; + } + DEBUGASSERT(stream->h1.req); + + *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); + if(*err) { + nwritten = -1; + goto out; + } + /* no longer needed */ + Curl_h1_req_parse_free(&stream->h1); + + nva = Curl_dynhds_to_nva(&h2_headers, &nheader); + if(!nva) { + *err = CURLE_OUT_OF_MEMORY; + nwritten = -1; + goto out; + } + + h2_pri_spec(data, &pri_spec); + if(!nghttp2_session_check_request_allowed(ctx->h2)) + CURL_TRC_CF(data, cf, "send request NOT allowed (via nghttp2)"); + + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + case HTTPREQ_PUT: + if(data->state.infilesize != -1) + stream->upload_left = data->state.infilesize; + else + /* data sending without specifying the data amount up front */ + stream->upload_left = -1; /* unknown */ + + data_prd.read_callback = req_body_read_callback; + data_prd.source.ptr = NULL; + stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, + &data_prd, data); + break; + default: + stream->upload_left = 0; /* no request body */ + stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, + NULL, data); + } + + if(stream_id < 0) { + CURL_TRC_CF(data, cf, "send: nghttp2_submit_request error (%s)%u", + nghttp2_strerror(stream_id), stream_id); + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + +#define MAX_ACC 60000 /* <64KB to account for some overhead */ + if(Curl_trc_is_verbose(data)) { + size_t acc = 0; + + infof(data, "[HTTP/2] [%d] OPENED stream for %s", + stream_id, data->state.url); + for(i = 0; i < nheader; ++i) { + acc += nva[i].namelen + nva[i].valuelen; + + infof(data, "[HTTP/2] [%d] [%.*s: %.*s]", stream_id, + (int)nva[i].namelen, nva[i].name, + (int)nva[i].valuelen, nva[i].value); + } + + if(acc > MAX_ACC) { + infof(data, "[HTTP/2] Warning: The cumulative length of all " + "headers exceeds %d bytes and that could cause the " + "stream to be rejected.", MAX_ACC); + } + } + + stream->id = stream_id; + stream->local_window_size = H2_STREAM_WINDOW_SIZE; + if(data->set.max_recv_speed) { + /* We are asked to only receive `max_recv_speed` bytes per second. + * Let's limit our stream window size around that, otherwise the server + * will send in large bursts only. We make the window 50% larger to + * allow for data in flight and avoid stalling. */ + curl_off_t n = (((data->set.max_recv_speed - 1) / H2_CHUNK_SIZE) + 1); + n += CURLMAX((n/2), 1); + if(n < (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) && + n < (UINT_MAX / H2_CHUNK_SIZE)) { + stream->local_window_size = (uint32_t)n * H2_CHUNK_SIZE; + } + } + + body = (const char *)buf + nwritten; + bodylen = len - nwritten; + + if(bodylen) { + /* We have request body to send in DATA frame */ + ssize_t n = Curl_bufq_write(&stream->sendbuf, body, bodylen, err); + if(n < 0) { + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + nwritten += n; + } + +out: + CURL_TRC_CF(data, cf, "[%d] submit -> %zd, %d", + stream? stream->id : -1, nwritten, *err); + Curl_safefree(nva); + *pstream = stream; + Curl_dynhds_free(&h2_headers); + return nwritten; +} + +static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H2_STREAM_CTX(data); + struct cf_call_data save; + int rv; + ssize_t nwritten; + CURLcode result; + int blocked = 0, was_blocked = 0; + + CF_DATA_SAVE(save, cf, data); + + if(stream && stream->id != -1) { + if(stream->upload_blocked_len) { + /* the data in `buf` has already been submitted or added to the + * buffers, but have been EAGAINed on the last invocation. */ + /* TODO: this assertion triggers in OSSFuzz runs and it is not + * clear why. Disable for now to let OSSFuzz continue its tests. */ + DEBUGASSERT(len >= stream->upload_blocked_len); + if(len < stream->upload_blocked_len) { + /* Did we get called again with a smaller `len`? This should not + * happen. We are not prepared to handle that. */ + failf(data, "HTTP/2 send again with decreased length (%zd vs %zd)", + len, stream->upload_blocked_len); + *err = CURLE_HTTP2; + nwritten = -1; + goto out; + } + nwritten = (ssize_t)stream->upload_blocked_len; + stream->upload_blocked_len = 0; + was_blocked = 1; + } + else if(stream->closed) { + if(stream->resp_hds_complete) { + /* Server decided to close the stream after having sent us a findl + * response. This is valid if it is not interested in the request + * body. This happens on 30x or 40x responses. + * We silently discard the data sent, since this is not a transport + * error situation. */ + CURL_TRC_CF(data, cf, "[%d] discarding data" + "on closed stream with response", stream->id); + *err = CURLE_OK; + nwritten = (ssize_t)len; + goto out; + } + infof(data, "stream %u closed", stream->id); + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + else { + /* If stream_id != -1, we have dispatched request HEADERS and + * optionally request body, and now are going to send or sending + * more request body in DATA frame */ + nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err); + if(nwritten < 0 && *err != CURLE_AGAIN) + goto out; + } + + if(!Curl_bufq_is_empty(&stream->sendbuf)) { + /* req body data is buffered, resume the potentially suspended stream */ + rv = nghttp2_session_resume_data(ctx->h2, stream->id); + if(nghttp2_is_fatal(rv)) { + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + } + } + else { + nwritten = h2_submit(&stream, cf, data, buf, len, err); + if(nwritten < 0) { + goto out; + } + DEBUGASSERT(stream); + } + + /* Call the nghttp2 send loop and flush to write ALL buffered data, + * headers and/or request body completely out to the network */ + result = h2_progress_egress(cf, data); + /* if the stream has been closed in egress handling (nghttp2 does that + * when it does not like the headers, for example */ + if(stream && stream->closed && !was_blocked) { + infof(data, "stream %u closed", stream->id); + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + else if(result == CURLE_AGAIN) { + blocked = 1; + } + else if(result) { + *err = result; + nwritten = -1; + goto out; + } + else if(stream && !Curl_bufq_is_empty(&stream->sendbuf)) { + /* although we wrote everything that nghttp2 wants to send now, + * there is data left in our stream send buffer unwritten. This may + * be due to the stream's HTTP/2 flow window being exhausted. */ + blocked = 1; + } + + if(stream && blocked && nwritten > 0) { + /* Unable to send all data, due to connection blocked or H2 window + * exhaustion. Data is left in our stream buffer, or nghttp2's internal + * frame buffer or our network out buffer. */ + size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2, + stream->id); + /* Whatever the cause, we need to return CURL_EAGAIN for this call. + * We have unwritten state that needs us being invoked again and EAGAIN + * is the only way to ensure that. */ + stream->upload_blocked_len = nwritten; + CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) BLOCK: win %u/%zu " + "blocked_len=%zu", + stream->id, len, + nghttp2_session_get_remote_window_size(ctx->h2), rwin, + nwritten); + *err = CURLE_AGAIN; + nwritten = -1; + goto out; + } + else if(should_close_session(ctx)) { + /* nghttp2 thinks this session is done. If the stream has not been + * closed, this is an error state for out transfer */ + if(stream->closed) { + nwritten = http2_handle_stream_close(cf, data, stream, err); + } + else { + CURL_TRC_CF(data, cf, "send: nothing to do in this session"); + *err = CURLE_HTTP2; + nwritten = -1; + } + } + +out: + if(stream) { + CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %zd, %d, " + "upload_left=%" CURL_FORMAT_CURL_OFF_T ", " + "h2 windows %d-%d (stream-conn), " + "buffers %zu-%zu (stream-conn)", + stream->id, len, nwritten, *err, + stream->upload_left, + nghttp2_session_get_stream_remote_window_size( + ctx->h2, stream->id), + nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&stream->sendbuf), + Curl_bufq_len(&ctx->outbufq)); + } + else { + CURL_TRC_CF(data, cf, "cf_send(len=%zu) -> %zd, %d, " + "connection-window=%d, nw_send_buffer(%zu)", + len, nwritten, *err, + nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&ctx->outbufq)); + } + CF_DATA_RESTORE(cf, save); + return nwritten; +} + +static void cf_h2_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_h2_ctx *ctx = cf->ctx; + bool want_recv = CURL_WANT_RECV(data); + bool want_send = CURL_WANT_SEND(data); + + if(ctx->h2 && (want_recv || want_send)) { + struct stream_ctx *stream = H2_STREAM_CTX(data); + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); + struct cf_call_data save; + bool c_exhaust, s_exhaust; + + CF_DATA_SAVE(save, cf, data); + c_exhaust = !nghttp2_session_get_remote_window_size(ctx->h2); + s_exhaust = stream && stream->id >= 0 && + !nghttp2_session_get_stream_remote_window_size(ctx->h2, + stream->id); + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + (!c_exhaust && nghttp2_session_want_write(ctx->h2)); + + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } +} + +static CURLcode cf_h2_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_h2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* Connect the lower filters first */ + if(!cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + } + + *done = FALSE; + + CF_DATA_SAVE(save, cf, data); + if(!ctx->h2) { + result = cf_h2_ctx_init(cf, data, FALSE); + if(result) + goto out; + } + + result = h2_progress_ingress(cf, data); + if(result) + goto out; + + /* Send out our SETTINGS and ACKs and such. If that blocks, we + * have it buffered and can count this filter as being connected */ + result = h2_progress_egress(cf, data); + if(result == CURLE_AGAIN) + result = CURLE_OK; + else if(result) + goto out; + + *done = TRUE; + cf->connected = TRUE; + result = CURLE_OK; + +out: + CURL_TRC_CF(data, cf, "cf_connect() -> %d, %d, ", result, *done); + CF_DATA_RESTORE(cf, save); + return result; +} + +static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + + if(ctx) { + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + cf_h2_ctx_clear(ctx); + CF_DATA_RESTORE(cf, save); + } + if(cf->next) + cf->next->cft->do_close(cf->next, data); +} + +static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + + (void)data; + if(ctx) { + cf_h2_ctx_free(ctx); + cf->ctx = NULL; + } +} + +static CURLcode http2_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ +#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H2_STREAM_CTX(data); + + DEBUGASSERT(data); + if(ctx && ctx->h2 && stream) { + uint32_t window = pause? 0 : stream->local_window_size; + + int rv = nghttp2_session_set_local_window_size(ctx->h2, + NGHTTP2_FLAG_NONE, + stream->id, + window); + if(rv) { + failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", + nghttp2_strerror(rv), rv); + return CURLE_HTTP2; + } + + if(!pause) + drain_stream(cf, data, stream); + + /* attempt to send the window update */ + (void)h2_progress_egress(cf, data); + + if(!pause) { + /* Unpausing a h2 transfer, requires it to be run again. The server + * may send new DATA on us increasing the flow window, and it may + * not. We may have already buffered and exhausted the new window + * by operating on things in flight during the handling of other + * transfers. */ + drain_stream(cf, data, stream); + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + DEBUGF(infof(data, "Set HTTP/2 window size to %u for stream %u", + window, stream->id)); + +#ifdef DEBUGBUILD + { + /* read out the stream local window again */ + uint32_t window2 = + nghttp2_session_get_stream_local_window_size(ctx->h2, + stream->id); + DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u", + window2, stream->id)); + } +#endif + } +#endif + return CURLE_OK; +} + +static CURLcode cf_h2_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + CURLcode result = CURLE_OK; + struct cf_call_data save; + + (void)arg2; + + CF_DATA_SAVE(save, cf, data); + switch(event) { + case CF_CTRL_DATA_SETUP: + break; + case CF_CTRL_DATA_PAUSE: + result = http2_data_pause(cf, data, (arg1 != 0)); + break; + case CF_CTRL_DATA_DONE_SEND: + result = http2_data_done_send(cf, data); + break; + case CF_CTRL_DATA_DETACH: + http2_data_done(cf, data, TRUE); + break; + case CF_CTRL_DATA_DONE: + http2_data_done(cf, data, arg1 != 0); + break; + default: + break; + } + CF_DATA_RESTORE(cf, save); + return result; +} + +static bool cf_h2_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H2_STREAM_CTX(data); + + if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq) + || (stream && !Curl_bufq_is_empty(&stream->sendbuf)) + || (stream && !Curl_bufq_is_empty(&stream->recvbuf)))) + return TRUE; + return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE; +} + +static bool cf_h2_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_h2_ctx *ctx = cf->ctx; + CURLcode result; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + result = (ctx && ctx->h2 && http2_connisalive(cf, data, input_pending)); + CURL_TRC_CF(data, cf, "conn alive -> %d, input_pending=%d", + result, *input_pending); + CF_DATA_RESTORE(cf, save); + return result; +} + +static CURLcode cf_h2_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + result = http2_send_ping(cf, data); + CF_DATA_RESTORE(cf, save); + return result; +} + +static CURLcode cf_h2_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct cf_call_data save; + size_t effective_max; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: + DEBUGASSERT(pres1); + + CF_DATA_SAVE(save, cf, data); + if(nghttp2_session_check_request_allowed(ctx->h2) == 0) { + /* the limit is what we have in use right now */ + effective_max = CONN_INUSE(cf->conn); + } + else { + effective_max = ctx->max_concurrent_streams; + } + *pres1 = (effective_max > INT_MAX)? INT_MAX : (int)effective_max; + CF_DATA_RESTORE(cf, save); + return CURLE_OK; + default: + break; + } + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +struct Curl_cftype Curl_cft_nghttp2 = { + "HTTP/2", + CF_TYPE_MULTIPLEX, + CURL_LOG_LVL_NONE, + cf_h2_destroy, + cf_h2_connect, + cf_h2_close, + Curl_cf_def_get_host, + cf_h2_adjust_pollset, + cf_h2_data_pending, + cf_h2_send, + cf_h2_recv, + cf_h2_cntrl, + cf_h2_is_alive, + cf_h2_keep_alive, + cf_h2_query, +}; + +static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = NULL; + struct cf_h2_ctx *ctx; + CURLcode result = CURLE_OUT_OF_MEMORY; + + DEBUGASSERT(data->conn); + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) + goto out; + + result = Curl_cf_create(&cf, &Curl_cft_nghttp2, ctx); + if(result) + goto out; + + Curl_conn_cf_add(data, conn, sockindex, cf); + result = CURLE_OK; + +out: + if(result) + cf_h2_ctx_free(ctx); + *pcf = result? NULL : cf; + return result; +} + +static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf_h2 = NULL; + struct cf_h2_ctx *ctx; + CURLcode result = CURLE_OUT_OF_MEMORY; + + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) + goto out; + + result = Curl_cf_create(&cf_h2, &Curl_cft_nghttp2, ctx); + if(result) + goto out; + + Curl_conn_cf_insert_after(cf, cf_h2); + result = CURLE_OK; + +out: + if(result) + cf_h2_ctx_free(ctx); + return result; +} + +static bool Curl_cf_is_http2(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + (void)data; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_nghttp2) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT) + return FALSE; + } + return FALSE; +} + +bool Curl_conn_is_http2(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ + return conn? Curl_cf_is_http2(conn->cfilter[sockindex], data) : FALSE; +} + +bool Curl_http2_may_switch(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + (void)sockindex; + if(!Curl_conn_is_http2(data, conn, sockindex) && + data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + /* We don't support HTTP/2 proxies yet. Also it's debatable + whether or not this setting should apply to HTTP/2 proxies. */ + infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); + return FALSE; + } +#endif + return TRUE; + } + return FALSE; +} + +CURLcode Curl_http2_switch(struct Curl_easy *data, + struct connectdata *conn, int sockindex) +{ + struct Curl_cfilter *cf; + CURLcode result; + + DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); + DEBUGF(infof(data, "switching to HTTP/2")); + + result = http2_cfilter_add(&cf, data, conn, sockindex); + if(result) + return result; + + result = cf_h2_ctx_init(cf, data, FALSE); + if(result) + return result; + + conn->httpversion = 20; /* we know we're on HTTP/2 now */ + conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + conn->bundle->multiuse = BUNDLE_MULTIPLEX; + Curl_multi_connchanged(data->multi); + + if(cf->next) { + bool done; + return Curl_conn_cf_connect(cf, data, FALSE, &done); + } + return CURLE_OK; +} + +CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct Curl_cfilter *cf_h2; + CURLcode result; + + DEBUGASSERT(!Curl_cf_is_http2(cf, data)); + + result = http2_cfilter_insert_after(cf, data); + if(result) + return result; + + cf_h2 = cf->next; + result = cf_h2_ctx_init(cf_h2, data, FALSE); + if(result) + return result; + + cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */ + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; + Curl_multi_connchanged(data->multi); + + if(cf_h2->next) { + bool done; + return Curl_conn_cf_connect(cf_h2, data, FALSE, &done); + } + return CURLE_OK; +} + +CURLcode Curl_http2_upgrade(struct Curl_easy *data, + struct connectdata *conn, int sockindex, + const char *mem, size_t nread) +{ + struct Curl_cfilter *cf; + struct cf_h2_ctx *ctx; + CURLcode result; + + DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); + DEBUGF(infof(data, "upgrading to HTTP/2")); + DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED); + + result = http2_cfilter_add(&cf, data, conn, sockindex); + if(result) + return result; + + DEBUGASSERT(cf->cft == &Curl_cft_nghttp2); + ctx = cf->ctx; + + result = cf_h2_ctx_init(cf, data, TRUE); + if(result) + return result; + + if(nread > 0) { + /* Remaining data from the protocol switch reply is already using + * the switched protocol, ie. HTTP/2. We add that to the network + * inbufq. */ + ssize_t copied; + + copied = Curl_bufq_write(&ctx->inbufq, + (const unsigned char *)mem, nread, &result); + if(copied < 0) { + failf(data, "error on copying HTTP Upgrade response: %d", result); + return CURLE_RECV_ERROR; + } + if((size_t)copied < nread) { + failf(data, "connection buffer size could not take all data " + "from HTTP Upgrade response header: copied=%zd, datalen=%zu", + copied, nread); + return CURLE_HTTP2; + } + infof(data, "Copied HTTP/2 data in stream buffer to connection buffer" + " after upgrade: len=%zu", nread); + } + + conn->httpversion = 20; /* we know we're on HTTP/2 now */ + conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + conn->bundle->multiuse = BUNDLE_MULTIPLEX; + Curl_multi_connchanged(data->multi); + + if(cf->next) { + bool done; + return Curl_conn_cf_connect(cf, data, FALSE, &done); + } + return CURLE_OK; +} + +/* Only call this function for a transfer that already got an HTTP/2 + CURLE_HTTP2_STREAM error! */ +bool Curl_h2_http_1_1_error(struct Curl_easy *data) +{ + struct stream_ctx *stream = H2_STREAM_CTX(data); + return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED); +} + +#else /* !USE_NGHTTP2 */ + +/* Satisfy external references even if http2 is not compiled in. */ +#include <curl/curl.h> + +char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) +{ + (void) h; + (void) num; + return NULL; +} + +char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) +{ + (void) h; + (void) header; + return NULL; +} + +#endif /* USE_NGHTTP2 */ diff --git a/Utilities/cmcurl/lib/http2.h b/Utilities/cmcurl/lib/http2.h new file mode 100644 index 0000000..80e1834 --- /dev/null +++ b/Utilities/cmcurl/lib/http2.h @@ -0,0 +1,77 @@ +#ifndef HEADER_CURL_HTTP2_H +#define HEADER_CURL_HTTP2_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_NGHTTP2 +#include "http.h" + +/* value for MAX_CONCURRENT_STREAMS we use until we get an updated setting + from the peer */ +#define DEFAULT_MAX_CONCURRENT_STREAMS 100 + +/* + * Store nghttp2 version info in this buffer. + */ +void Curl_http2_ver(char *p, size_t len); + +CURLcode Curl_http2_request_upgrade(struct dynbuf *req, + struct Curl_easy *data); + +/* returns true if the HTTP/2 stream error was HTTP_1_1_REQUIRED */ +bool Curl_h2_http_1_1_error(struct Curl_easy *data); + +bool Curl_conn_is_http2(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); +bool Curl_http2_may_switch(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + +CURLcode Curl_http2_switch(struct Curl_easy *data, + struct connectdata *conn, int sockindex); + +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, + const char *ptr, size_t nread); + +extern struct Curl_cftype Curl_cft_nghttp2; + +#else /* USE_NGHTTP2 */ + +#define Curl_cf_is_http2(a,b) FALSE +#define Curl_conn_is_http2(a,b,c) FALSE +#define Curl_http2_may_switch(a,b,c) FALSE + +#define Curl_http2_request_upgrade(x,y) CURLE_UNSUPPORTED_PROTOCOL +#define Curl_http2_switch(a,b,c) CURLE_UNSUPPORTED_PROTOCOL +#define Curl_http2_upgrade(a,b,c,d,e) CURLE_UNSUPPORTED_PROTOCOL +#define Curl_h2_http_1_1_error(x) 0 +#endif + +#endif /* HEADER_CURL_HTTP2_H */ diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.c b/Utilities/cmcurl/lib/http_aws_sigv4.c new file mode 100644 index 0000000..b673055 --- /dev/null +++ b/Utilities/cmcurl/lib/http_aws_sigv4.c @@ -0,0 +1,808 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.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_AWS) + +#include "urldata.h" +#include "strcase.h" +#include "strdup.h" +#include "http_aws_sigv4.h" +#include "curl_sha256.h" +#include "transfer.h" +#include "parsedate.h" +#include "sendf.h" +#include "escape.h" + +#include <time.h> + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#include "slist.h" + +#define HMAC_SHA256(k, kl, d, dl, o) \ + do { \ + result = Curl_hmacit(Curl_HMAC_SHA256, \ + (unsigned char *)k, \ + kl, \ + (unsigned char *)d, \ + dl, o); \ + if(result) { \ + goto fail; \ + } \ + } while(0) + +#define TIMESTAMP_SIZE 17 + +/* hex-encoded with trailing null */ +#define SHA256_HEX_LENGTH (2 * SHA256_DIGEST_LENGTH + 1) + +static void sha256_to_hex(char *dst, unsigned char *sha) +{ + Curl_hexencode(sha, SHA256_DIGEST_LENGTH, + (unsigned char *)dst, SHA256_HEX_LENGTH); +} + +static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr) +{ + char *tmp = Curl_checkheaders(data, sig_hdr, strlen(sig_hdr)); + + if(tmp) + return tmp; + return Curl_checkheaders(data, STRCONST("Date")); +} + +/* remove whitespace, and lowercase all headers */ +static void trim_headers(struct curl_slist *head) +{ + struct curl_slist *l; + for(l = head; l; l = l->next) { + char *value; /* to read from */ + char *store; + size_t colon = strcspn(l->data, ":"); + Curl_strntolower(l->data, l->data, colon); + + value = &l->data[colon]; + if(!*value) + continue; + ++value; + store = value; + + /* skip leading whitespace */ + while(*value && ISBLANK(*value)) + value++; + + while(*value) { + int space = 0; + while(*value && ISBLANK(*value)) { + value++; + space++; + } + if(space) { + /* replace any number of consecutive whitespace with a single space, + unless at the end of the string, then nothing */ + if(*value) + *store++ = ' '; + } + else + *store++ = *value++; + } + *store = 0; /* null terminate */ + } +} + +/* maximum length for the aws sivg4 parts */ +#define MAX_SIGV4_LEN 64 +#define MAX_SIGV4_LEN_TXT "64" + +#define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date")) + +#define MAX_HOST_LEN 255 +/* FQDN + host: */ +#define FULL_HOST_LEN (MAX_HOST_LEN + sizeof("host:")) + +/* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */ +#define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1) + +/* 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, + char *provider1, + char **date_header, + char *content_sha256_header, + struct dynbuf *canonical_headers, + struct dynbuf *signed_headers) +{ + char date_hdr_key[DATE_HDR_KEY_LEN]; + char date_full_hdr[DATE_FULL_HDR_LEN]; + struct curl_slist *head = NULL; + struct curl_slist *tmp_head = NULL; + CURLcode ret = CURLE_OUT_OF_MEMORY; + struct curl_slist *l; + int again = 1; + + /* provider1 mid */ + Curl_strntolower(provider1, provider1, strlen(provider1)); + provider1[0] = Curl_raw_toupper(provider1[0]); + + msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%s-Date", provider1); + + /* provider1 lowercase */ + Curl_strntolower(provider1, provider1, 1); /* first byte only */ + msnprintf(date_full_hdr, DATE_FULL_HDR_LEN, + "x-%s-date:%s", provider1, timestamp); + + if(Curl_checkheaders(data, STRCONST("Host"))) { + head = NULL; + } + else { + char full_host[FULL_HOST_LEN + 1]; + + if(data->state.aptr.host) { + size_t pos; + + if(strlen(data->state.aptr.host) > FULL_HOST_LEN) { + ret = CURLE_URL_MALFORMAT; + goto fail; + } + strcpy(full_host, data->state.aptr.host); + /* remove /r/n as the separator for canonical request must be '\n' */ + pos = strcspn(full_host, "\n\r"); + full_host[pos] = 0; + } + else { + if(strlen(hostname) > MAX_HOST_LEN) { + ret = CURLE_URL_MALFORMAT; + goto fail; + } + msnprintf(full_host, FULL_HOST_LEN, "host:%s", hostname); + } + + head = curl_slist_append(NULL, full_host); + if(!head) + goto fail; + } + + + if(*content_sha256_header) { + tmp_head = curl_slist_append(head, content_sha256_header); + if(!tmp_head) + goto fail; + head = tmp_head; + } + + /* copy user headers to our header list. the logic is based on how http.c + handles user headers. + + user headers in format 'name:' with no value are used to signal that an + internal header of that name should be removed. those user headers are not + added to this list. + + user headers in format 'name;' with no value are used to signal that a + header of that name with no value should be sent. those user headers are + added to this list but in the format that they will be sent, ie the + semi-colon is changed to a colon for format 'name:'. + + user headers with a value of whitespace only, or without a colon or + semi-colon, are not added to this list. + */ + for(l = data->set.headers; l; l = l->next) { + char *dupdata, *ptr; + char *sep = strchr(l->data, ':'); + if(!sep) + sep = strchr(l->data, ';'); + if(!sep || (*sep == ':' && !*(sep + 1))) + continue; + for(ptr = sep + 1; ISSPACE(*ptr); ++ptr) + ; + if(!*ptr && ptr != sep + 1) /* a value of whitespace only */ + continue; + dupdata = strdup(l->data); + if(!dupdata) + goto fail; + dupdata[sep - l->data] = ':'; + tmp_head = Curl_slist_append_nodup(head, dupdata); + if(!tmp_head) { + free(dupdata); + goto fail; + } + head = tmp_head; + } + + trim_headers(head); + + *date_header = find_date_hdr(data, date_hdr_key); + if(!*date_header) { + tmp_head = curl_slist_append(head, date_full_hdr); + if(!tmp_head) + goto fail; + head = tmp_head; + *date_header = curl_maprintf("%s: %s\r\n", date_hdr_key, timestamp); + } + else { + char *value; + + value = strchr(*date_header, ':'); + if(!value) { + *date_header = NULL; + goto fail; + } + ++value; + while(ISBLANK(*value)) + ++value; + strncpy(timestamp, value, TIMESTAMP_SIZE - 1); + timestamp[TIMESTAMP_SIZE - 1] = 0; + *date_header = NULL; + } + + /* alpha-sort in a case sensitive manner */ + do { + again = 0; + for(l = head; l; l = l->next) { + struct curl_slist *next = l->next; + + if(next && strcmp(l->data, next->data) > 0) { + char *tmp = l->data; + + l->data = next->data; + next->data = tmp; + again = 1; + } + } + } while(again); + + for(l = head; l; l = l->next) { + char *tmp; + + if(Curl_dyn_add(canonical_headers, l->data)) + goto fail; + if(Curl_dyn_add(canonical_headers, "\n")) + goto fail; + + tmp = strchr(l->data, ':'); + if(tmp) + *tmp = 0; + + if(l != head) { + if(Curl_dyn_add(signed_headers, ";")) + goto fail; + } + if(Curl_dyn_add(signed_headers, l->data)) + goto fail; + } + + ret = CURLE_OK; +fail: + curl_slist_free_all(head); + + return ret; +} + +#define CONTENT_SHA256_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Content-Sha256")) +/* add 2 for ": " between header name and value */ +#define CONTENT_SHA256_HDR_LEN (CONTENT_SHA256_KEY_LEN + 2 + \ + SHA256_HEX_LENGTH) + +/* try to parse a payload hash from the content-sha256 header */ +static char *parse_content_sha_hdr(struct Curl_easy *data, + const char *provider1, + size_t *value_len) +{ + char key[CONTENT_SHA256_KEY_LEN]; + size_t key_len; + char *value; + size_t len; + + key_len = msnprintf(key, sizeof(key), "x-%s-content-sha256", provider1); + + value = Curl_checkheaders(data, key, key_len); + if(!value) + return NULL; + + value = strchr(value, ':'); + if(!value) + return NULL; + ++value; + + while(*value && ISBLANK(*value)) + ++value; + + len = strlen(value); + while(len > 0 && ISBLANK(value[len-1])) + --len; + + *value_len = len; + return value; +} + +static CURLcode calc_payload_hash(struct Curl_easy *data, + unsigned char *sha_hash, char *sha_hex) +{ + const char *post_data = data->set.postfields; + size_t post_data_len = 0; + CURLcode result; + + if(post_data) { + if(data->set.postfieldsize < 0) + post_data_len = strlen(post_data); + else + post_data_len = (size_t)data->set.postfieldsize; + } + result = Curl_sha256it(sha_hash, (const unsigned char *) post_data, + post_data_len); + if(!result) + sha256_to_hex(sha_hex, sha_hash); + return result; +} + +#define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD" + +static CURLcode calc_s3_payload_hash(struct Curl_easy *data, + Curl_HttpReq httpreq, char *provider1, + unsigned char *sha_hash, + char *sha_hex, char *header) +{ + bool empty_method = (httpreq == HTTPREQ_GET || httpreq == HTTPREQ_HEAD); + /* The request method or filesize indicate no request payload */ + bool empty_payload = (empty_method || data->set.filesize == 0); + /* The POST payload is in memory */ + bool post_payload = (httpreq == HTTPREQ_POST && data->set.postfields); + CURLcode ret = CURLE_OUT_OF_MEMORY; + + if(empty_payload || post_payload) { + /* Calculate a real hash when we know the request payload */ + ret = calc_payload_hash(data, sha_hash, sha_hex); + if(ret) + goto fail; + } + else { + /* Fall back to s3's UNSIGNED-PAYLOAD */ + size_t len = sizeof(S3_UNSIGNED_PAYLOAD) - 1; + DEBUGASSERT(len < SHA256_HEX_LENGTH); /* 16 < 65 */ + memcpy(sha_hex, S3_UNSIGNED_PAYLOAD, len); + sha_hex[len] = 0; + } + + /* format the required content-sha256 header */ + msnprintf(header, CONTENT_SHA256_HDR_LEN, + "x-%s-content-sha256: %s", provider1, sha_hex); + + ret = CURLE_OK; +fail: + return ret; +} + +struct pair { + const char *p; + size_t len; +}; + +static int compare_func(const void *a, const void *b) +{ + const struct pair *aa = a; + const struct pair *bb = b; + /* If one element is empty, the other is always sorted higher */ + if(aa->len == 0) + return -1; + if(bb->len == 0) + return 1; + return strncmp(aa->p, bb->p, aa->len < bb->len ? aa->len : bb->len); +} + +#define MAX_QUERYPAIRS 64 + +static CURLcode canon_query(struct Curl_easy *data, + const char *query, struct dynbuf *dq) +{ + CURLcode result = CURLE_OK; + int entry = 0; + int i; + const char *p = query; + struct pair array[MAX_QUERYPAIRS]; + struct pair *ap = &array[0]; + if(!query) + return result; + + /* sort the name=value pairs first */ + do { + char *amp; + entry++; + ap->p = p; + amp = strchr(p, '&'); + if(amp) + ap->len = amp - p; /* excluding the ampersand */ + else { + ap->len = strlen(p); + break; + } + ap++; + p = amp + 1; + } while(entry < MAX_QUERYPAIRS); + if(entry == MAX_QUERYPAIRS) { + /* too many query pairs for us */ + failf(data, "aws-sigv4: too many query pairs in URL"); + return CURLE_URL_MALFORMAT; + } + + qsort(&array[0], entry, sizeof(struct pair), compare_func); + + ap = &array[0]; + for(i = 0; !result && (i < entry); i++, ap++) { + size_t len; + const char *q = ap->p; + bool found_equals = false; + if(!ap->len) + continue; + for(len = ap->len; len && !result; q++, len--) { + if(ISALNUM(*q)) + result = Curl_dyn_addn(dq, q, 1); + else { + switch(*q) { + case '-': + case '.': + case '_': + case '~': + /* allowed as-is */ + result = Curl_dyn_addn(dq, q, 1); + break; + case '=': + /* allowed as-is */ + result = Curl_dyn_addn(dq, q, 1); + found_equals = true; + break; + case '%': + /* uppercase the following if hexadecimal */ + if(ISXDIGIT(q[1]) && ISXDIGIT(q[2])) { + char tmp[3]="%"; + tmp[1] = Curl_raw_toupper(q[1]); + tmp[2] = Curl_raw_toupper(q[2]); + result = Curl_dyn_addn(dq, tmp, 3); + q += 2; + len -= 2; + } + else + /* '%' without a following two-digit hex, encode it */ + result = Curl_dyn_addn(dq, "%25", 3); + break; + default: { + /* URL encode */ + const char hex[] = "0123456789ABCDEF"; + char out[3]={'%'}; + out[1] = hex[((unsigned char)*q)>>4]; + out[2] = hex[*q & 0xf]; + result = Curl_dyn_addn(dq, out, 3); + break; + } + } + } + } + if(!result && !found_equals) { + /* queries without value still need an equals */ + result = Curl_dyn_addn(dq, "=", 1); + } + if(!result && i < entry - 1) { + /* insert ampersands between query pairs */ + result = Curl_dyn_addn(dq, "&", 1); + } + } + return result; +} + + +CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) +{ + CURLcode result = CURLE_OUT_OF_MEMORY; + struct connectdata *conn = data->conn; + size_t len; + const char *arg; + char provider0[MAX_SIGV4_LEN + 1]=""; + char provider1[MAX_SIGV4_LEN + 1]=""; + char region[MAX_SIGV4_LEN + 1]=""; + char service[MAX_SIGV4_LEN + 1]=""; + bool sign_as_s3 = false; + const char *hostname = conn->host.name; + time_t clock; + struct tm tm; + char timestamp[TIMESTAMP_SIZE]; + char date[9]; + struct dynbuf canonical_headers; + struct dynbuf signed_headers; + struct dynbuf canonical_query; + char *date_header = NULL; + Curl_HttpReq httpreq; + const char *method = NULL; + char *payload_hash = NULL; + size_t payload_hash_len = 0; + unsigned char sha_hash[SHA256_DIGEST_LENGTH]; + char sha_hex[SHA256_HEX_LENGTH]; + char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */ + char *canonical_request = NULL; + char *request_type = NULL; + char *credential_scope = NULL; + char *str_to_sign = NULL; + const char *user = data->state.aptr.user ? data->state.aptr.user : ""; + char *secret = NULL; + unsigned char sign0[SHA256_DIGEST_LENGTH] = {0}; + unsigned char sign1[SHA256_DIGEST_LENGTH] = {0}; + char *auth_headers = NULL; + + DEBUGASSERT(!proxy); + (void)proxy; + + if(Curl_checkheaders(data, STRCONST("Authorization"))) { + /* Authorization already present, Bailing out */ + return CURLE_OK; + } + + /* we init those buffers here, so goto fail will free initialized dynbuf */ + Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER); + Curl_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER); + Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER); + + /* + * Parameters parsing + * Google and Outscale use the same OSC or GOOG, + * but Amazon uses AWS and AMZ for header arguments. + * AWS is the default because most of non-amazon providers + * are still using aws:amz as a prefix. + */ + arg = data->set.str[STRING_AWS_SIGV4] ? + data->set.str[STRING_AWS_SIGV4] : "aws:amz"; + + /* provider1[:provider2[:region[:service]]] + + No string can be longer than N bytes of non-whitespace + */ + (void)sscanf(arg, "%" MAX_SIGV4_LEN_TXT "[^:]" + ":%" MAX_SIGV4_LEN_TXT "[^:]" + ":%" MAX_SIGV4_LEN_TXT "[^:]" + ":%" MAX_SIGV4_LEN_TXT "s", + provider0, provider1, region, service); + if(!provider0[0]) { + failf(data, "first aws-sigv4 provider can't be empty"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto fail; + } + else if(!provider1[0]) + strcpy(provider1, provider0); + + if(!service[0]) { + char *hostdot = strchr(hostname, '.'); + if(!hostdot) { + failf(data, "aws-sigv4: service missing in parameters and hostname"); + result = CURLE_URL_MALFORMAT; + goto fail; + } + len = hostdot - hostname; + if(len > MAX_SIGV4_LEN) { + failf(data, "aws-sigv4: service too long in hostname"); + result = CURLE_URL_MALFORMAT; + goto fail; + } + strncpy(service, hostname, len); + service[len] = '\0'; + + infof(data, "aws_sigv4: picked service %s from host", service); + + if(!region[0]) { + const char *reg = hostdot + 1; + const char *hostreg = strchr(reg, '.'); + if(!hostreg) { + failf(data, "aws-sigv4: region missing in parameters and hostname"); + result = CURLE_URL_MALFORMAT; + goto fail; + } + len = hostreg - reg; + if(len > MAX_SIGV4_LEN) { + failf(data, "aws-sigv4: region too long in hostname"); + result = CURLE_URL_MALFORMAT; + goto fail; + } + strncpy(region, reg, len); + region[len] = '\0'; + infof(data, "aws_sigv4: picked region %s from host", region); + } + } + + Curl_http_method(data, conn, &method, &httpreq); + + /* AWS S3 requires a x-amz-content-sha256 header, and supports special + * values like UNSIGNED-PAYLOAD */ + sign_as_s3 = (strcasecompare(provider0, "aws") && + strcasecompare(service, "s3")); + + payload_hash = parse_content_sha_hdr(data, provider1, &payload_hash_len); + + if(!payload_hash) { + if(sign_as_s3) + result = calc_s3_payload_hash(data, httpreq, provider1, sha_hash, + sha_hex, content_sha256_hdr); + else + result = calc_payload_hash(data, sha_hash, sha_hex); + if(result) + goto fail; + + payload_hash = sha_hex; + /* may be shorter than SHA256_HEX_LENGTH, like S3_UNSIGNED_PAYLOAD */ + payload_hash_len = strlen(sha_hex); + } + +#ifdef DEBUGBUILD + { + char *force_timestamp = getenv("CURL_FORCETIME"); + if(force_timestamp) + clock = 0; + else + time(&clock); + } +#else + time(&clock); +#endif + result = Curl_gmtime(clock, &tm); + if(result) { + goto fail; + } + if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + + result = make_headers(data, hostname, timestamp, provider1, + &date_header, content_sha256_hdr, + &canonical_headers, &signed_headers); + if(result) + goto fail; + + if(*content_sha256_hdr) { + /* make_headers() needed this without the \r\n for canonicalization */ + size_t hdrlen = strlen(content_sha256_hdr); + DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr)); + memcpy(content_sha256_hdr + hdrlen, "\r\n", 3); + } + + memcpy(date, timestamp, sizeof(date)); + date[sizeof(date) - 1] = 0; + + result = canon_query(data, data->state.up.query, &canonical_query); + if(result) + goto fail; + result = CURLE_OUT_OF_MEMORY; + + canonical_request = + curl_maprintf("%s\n" /* HTTPRequestMethod */ + "%s\n" /* CanonicalURI */ + "%s\n" /* CanonicalQueryString */ + "%s\n" /* CanonicalHeaders */ + "%s\n" /* SignedHeaders */ + "%.*s", /* HashedRequestPayload in hex */ + method, + data->state.up.path, + Curl_dyn_ptr(&canonical_query) ? + Curl_dyn_ptr(&canonical_query) : "", + Curl_dyn_ptr(&canonical_headers), + Curl_dyn_ptr(&signed_headers), + (int)payload_hash_len, payload_hash); + if(!canonical_request) + goto fail; + + DEBUGF(infof(data, "Canonical request: %s", canonical_request)); + + /* provider 0 lowercase */ + Curl_strntolower(provider0, provider0, strlen(provider0)); + request_type = curl_maprintf("%s4_request", provider0); + if(!request_type) + goto fail; + + credential_scope = curl_maprintf("%s/%s/%s/%s", + date, region, service, request_type); + if(!credential_scope) + goto fail; + + if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request, + strlen(canonical_request))) + goto fail; + + sha256_to_hex(sha_hex, sha_hash); + + /* provider 0 uppercase */ + Curl_strntoupper(provider0, provider0, strlen(provider0)); + + /* + * Google allows using RSA key instead of HMAC, so this code might change + * in the future. For now we only support HMAC. + */ + str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */ + "%s\n" /* RequestDateTime */ + "%s\n" /* CredentialScope */ + "%s", /* HashedCanonicalRequest in hex */ + provider0, + timestamp, + credential_scope, + sha_hex); + if(!str_to_sign) { + goto fail; + } + + /* provider 0 uppercase */ + secret = curl_maprintf("%s4%s", provider0, + data->state.aptr.passwd ? + data->state.aptr.passwd : ""); + if(!secret) + goto fail; + + HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0); + HMAC_SHA256(sign0, sizeof(sign0), region, strlen(region), sign1); + HMAC_SHA256(sign1, sizeof(sign1), service, strlen(service), sign0); + HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1); + HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0); + + sha256_to_hex(sha_hex, sign0); + + /* provider 0 uppercase */ + auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 " + "Credential=%s/%s, " + "SignedHeaders=%s, " + "Signature=%s\r\n" + /* + * date_header is added here, only if it wasn't + * user-specified (using CURLOPT_HTTPHEADER). + * date_header includes \r\n + */ + "%s" + "%s", /* optional sha256 header includes \r\n */ + provider0, + user, + credential_scope, + Curl_dyn_ptr(&signed_headers), + sha_hex, + date_header ? date_header : "", + content_sha256_hdr); + if(!auth_headers) { + goto fail; + } + + Curl_safefree(data->state.aptr.userpwd); + data->state.aptr.userpwd = auth_headers; + data->state.authhost.done = TRUE; + result = CURLE_OK; + +fail: + Curl_dyn_free(&canonical_query); + Curl_dyn_free(&canonical_headers); + Curl_dyn_free(&signed_headers); + free(canonical_request); + free(request_type); + free(credential_scope); + free(str_to_sign); + free(secret); + free(date_header); + return result; +} + +#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */ diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.h b/Utilities/cmcurl/lib/http_aws_sigv4.h new file mode 100644 index 0000000..57cc570 --- /dev/null +++ b/Utilities/cmcurl/lib/http_aws_sigv4.h @@ -0,0 +1,31 @@ +#ifndef HEADER_CURL_HTTP_AWS_SIGV4_H +#define HEADER_CURL_HTTP_AWS_SIGV4_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.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" + +/* this is for creating aws_sigv4 header output */ +CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy); + +#endif /* HEADER_CURL_HTTP_AWS_SIGV4_H */ diff --git a/Utilities/cmcurl/lib/http_chunks.c b/Utilities/cmcurl/lib/http_chunks.c new file mode 100644 index 0000000..acdb108 --- /dev/null +++ b/Utilities/cmcurl/lib/http_chunks.c @@ -0,0 +1,315 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_HTTP + +#include "urldata.h" /* it includes http_chunks.h */ +#include "sendf.h" /* for the client write stuff */ +#include "dynbuf.h" +#include "content_encoding.h" +#include "http.h" +#include "strtoofft.h" +#include "warnless.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Chunk format (simplified): + * + * <HEX SIZE>[ chunk extension ] CRLF + * <DATA> CRLF + * + * Highlights from RFC2616 section 3.6 say: + + The chunked encoding modifies the body of a message in order to + transfer it as a series of chunks, each with its own size indicator, + followed by an OPTIONAL trailer containing entity-header fields. This + allows dynamically produced content to be transferred along with the + information necessary for the recipient to verify that it has + received the full message. + + Chunked-Body = *chunk + last-chunk + trailer + CRLF + + chunk = chunk-size [ chunk-extension ] CRLF + chunk-data CRLF + chunk-size = 1*HEX + last-chunk = 1*("0") [ chunk-extension ] CRLF + + chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + chunk-ext-name = token + chunk-ext-val = token | quoted-string + chunk-data = chunk-size(OCTET) + trailer = *(entity-header CRLF) + + The chunk-size field is a string of hex digits indicating the size of + the chunk. The chunked encoding is ended by any chunk whose size is + zero, followed by the trailer, which is terminated by an empty line. + + */ + +void Curl_httpchunk_init(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + struct Curl_chunker *chunk = &conn->chunk; + chunk->hexindex = 0; /* start at 0 */ + chunk->state = CHUNK_HEX; /* we get hex first! */ + Curl_dyn_init(&conn->trailer, DYN_H1_TRAILER); +} + +/* + * chunk_read() returns a OK for normal operations, or a positive return code + * for errors. STOP means this sequence of chunks is complete. The 'wrote' + * argument is set to tell the caller how many bytes we actually passed to the + * client (for byte-counting and whatever). + * + * The states and the state-machine is further explained in the header file. + * + * This function always uses ASCII hex values to accommodate non-ASCII hosts. + * For example, 0x0d and 0x0a are used instead of '\r' and '\n'. + */ +CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, + char *buf, + size_t blen, + size_t *pconsumed, + CURLcode *extrap) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct Curl_chunker *ch = &conn->chunk; + struct SingleRequest *k = &data->req; + size_t piece; + + *pconsumed = 0; /* nothing's written yet */ + + /* the original data is written to the client, but we go on with the + chunk read process, to properly calculate the content length */ + if(data->set.http_te_skip && !k->ignorebody) { + result = Curl_client_write(data, CLIENTWRITE_BODY, buf, blen); + if(result) { + *extrap = result; + return CHUNKE_PASSTHRU_ERROR; + } + } + + while(blen) { + switch(ch->state) { + case CHUNK_HEX: + if(ISXDIGIT(*buf)) { + if(ch->hexindex < CHUNK_MAXNUM_LEN) { + ch->hexbuffer[ch->hexindex] = *buf; + buf++; + blen--; + ch->hexindex++; + } + else { + return CHUNKE_TOO_LONG_HEX; /* longer hex than we support */ + } + } + else { + char *endptr; + if(0 == ch->hexindex) + /* This is illegal data, we received junk where we expected + a hexadecimal digit. */ + return CHUNKE_ILLEGAL_HEX; + + /* blen and buf are unmodified */ + ch->hexbuffer[ch->hexindex] = 0; + + if(curlx_strtoofft(ch->hexbuffer, &endptr, 16, &ch->datasize)) + return CHUNKE_ILLEGAL_HEX; + ch->state = CHUNK_LF; /* now wait for the CRLF */ + } + break; + + case CHUNK_LF: + /* waiting for the LF after a chunk size */ + if(*buf == 0x0a) { + /* we're now expecting data to come, unless size was zero! */ + if(0 == ch->datasize) { + ch->state = CHUNK_TRAILER; /* now check for trailers */ + } + else + ch->state = CHUNK_DATA; + } + + buf++; + blen--; + break; + + 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. + */ + piece = blen; + if(ch->datasize < (curl_off_t)blen) + piece = curlx_sotouz(ch->datasize); + + /* Write the data portion available */ + if(!data->set.http_te_skip && !k->ignorebody) { + result = Curl_client_write(data, CLIENTWRITE_BODY, buf, piece); + + if(result) { + *extrap = result; + return CHUNKE_PASSTHRU_ERROR; + } + } + + *pconsumed += piece; + ch->datasize -= piece; /* decrease amount left to expect */ + buf += piece; /* move read pointer forward */ + blen -= piece; /* decrease space left in this round */ + + if(0 == ch->datasize) + /* end of data this round, we now expect a trailing CRLF */ + ch->state = CHUNK_POSTLF; + break; + + case CHUNK_POSTLF: + if(*buf == 0x0a) { + /* The last one before we go back to hex state and start all over. */ + Curl_httpchunk_init(data); /* sets state back to CHUNK_HEX */ + } + else if(*buf != 0x0d) + return CHUNKE_BAD_CHUNK; + buf++; + blen--; + break; + + case CHUNK_TRAILER: + if((*buf == 0x0d) || (*buf == 0x0a)) { + char *tr = Curl_dyn_ptr(&conn->trailer); + /* this is the end of a trailer, but if the trailer was zero bytes + there was no trailer and we move on */ + + if(tr) { + size_t trlen; + result = Curl_dyn_addn(&conn->trailer, (char *)STRCONST("\x0d\x0a")); + if(result) + return CHUNKE_OUT_OF_MEMORY; + + tr = Curl_dyn_ptr(&conn->trailer); + trlen = Curl_dyn_len(&conn->trailer); + if(!data->set.http_te_skip) { + result = Curl_client_write(data, + CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER, + tr, trlen); + if(result) { + *extrap = result; + return CHUNKE_PASSTHRU_ERROR; + } + } + Curl_dyn_reset(&conn->trailer); + ch->state = CHUNK_TRAILER_CR; + if(*buf == 0x0a) + /* already on the LF */ + break; + } + else { + /* no trailer, we're on the final CRLF pair */ + ch->state = CHUNK_TRAILER_POSTCR; + break; /* don't advance the pointer */ + } + } + else { + result = Curl_dyn_addn(&conn->trailer, buf, 1); + if(result) + return CHUNKE_OUT_OF_MEMORY; + } + buf++; + blen--; + break; + + case CHUNK_TRAILER_CR: + if(*buf == 0x0a) { + ch->state = CHUNK_TRAILER_POSTCR; + buf++; + blen--; + } + else + return CHUNKE_BAD_CHUNK; + break; + + case CHUNK_TRAILER_POSTCR: + /* We enter this state when a CR should arrive so we expect to + have to first pass a CR before we wait for LF */ + if((*buf != 0x0d) && (*buf != 0x0a)) { + /* not a CR then it must be another header in the trailer */ + ch->state = CHUNK_TRAILER; + break; + } + if(*buf == 0x0d) { + /* skip if CR */ + buf++; + blen--; + } + /* now wait for the final LF */ + ch->state = CHUNK_STOP; + break; + + case CHUNK_STOP: + if(*buf == 0x0a) { + blen--; + + /* Record the length of any data left in the end of the buffer + even if there's no more chunks to read */ + ch->datasize = blen; + + return CHUNKE_STOP; /* return stop */ + } + else + return CHUNKE_BAD_CHUNK; + } + } + return CHUNKE_OK; +} + +const char *Curl_chunked_strerror(CHUNKcode code) +{ + switch(code) { + default: + return "OK"; + case CHUNKE_TOO_LONG_HEX: + return "Too long hexadecimal number"; + case CHUNKE_ILLEGAL_HEX: + return "Illegal or missing hexadecimal sequence"; + case CHUNKE_BAD_CHUNK: + return "Malformed encoding found"; + case CHUNKE_PASSTHRU_ERROR: + DEBUGASSERT(0); /* never used */ + return ""; + case CHUNKE_BAD_ENCODING: + return "Bad content-encoding found"; + case CHUNKE_OUT_OF_MEMORY: + return "Out of memory"; + } +} + +#endif /* CURL_DISABLE_HTTP */ diff --git a/Utilities/cmcurl/lib/http_chunks.h b/Utilities/cmcurl/lib/http_chunks.h new file mode 100644 index 0000000..0a36f37 --- /dev/null +++ b/Utilities/cmcurl/lib/http_chunks.h @@ -0,0 +1,100 @@ +#ifndef HEADER_CURL_HTTP_CHUNKS_H +#define HEADER_CURL_HTTP_CHUNKS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +struct connectdata; + +/* + * The longest possible hexadecimal number we support in a chunked transfer. + * Neither RFC2616 nor the later HTTP specs define a maximum chunk size. + * For 64 bit curl_off_t we support 16 digits. For 32 bit, 8 digits. + */ +#define CHUNK_MAXNUM_LEN (SIZEOF_CURL_OFF_T * 2) + +typedef enum { + /* await and buffer all hexadecimal digits until we get one that isn't a + hexadecimal digit. When done, we go CHUNK_LF */ + CHUNK_HEX, + + /* wait for LF, ignore all else */ + CHUNK_LF, + + /* We eat the amount of data specified. When done, we move on to the + POST_CR state. */ + CHUNK_DATA, + + /* POSTLF should get a CR and then a LF and nothing else, then move back to + HEX as the CRLF combination marks the end of a chunk. A missing CR is no + big deal. */ + CHUNK_POSTLF, + + /* Used to mark that we're out of the game. NOTE: that there's a 'datasize' + field in the struct that will tell how many bytes that were not passed to + the client in the end of the last buffer! */ + CHUNK_STOP, + + /* At this point optional trailer headers can be found, unless the next line + is CRLF */ + CHUNK_TRAILER, + + /* A trailer CR has been found - next state is CHUNK_TRAILER_POSTCR. + Next char must be a LF */ + CHUNK_TRAILER_CR, + + /* A trailer LF must be found now, otherwise CHUNKE_BAD_CHUNK will be + signalled If this is an empty trailer CHUNKE_STOP will be signalled. + Otherwise the trailer will be broadcasted via Curl_client_write() and the + next state will be CHUNK_TRAILER */ + CHUNK_TRAILER_POSTCR +} ChunkyState; + +typedef enum { + CHUNKE_STOP = -1, + CHUNKE_OK = 0, + CHUNKE_TOO_LONG_HEX = 1, + CHUNKE_ILLEGAL_HEX, + CHUNKE_BAD_CHUNK, + CHUNKE_BAD_ENCODING, + CHUNKE_OUT_OF_MEMORY, + CHUNKE_PASSTHRU_ERROR, /* Curl_httpchunk_read() returns a CURLcode to use */ + CHUNKE_LAST +} CHUNKcode; + +const char *Curl_chunked_strerror(CHUNKcode code); + +struct Curl_chunker { + curl_off_t datasize; + ChunkyState state; + unsigned char hexindex; + char hexbuffer[ CHUNK_MAXNUM_LEN + 1]; /* +1 for null-terminator */ +}; + +/* The following functions are defined in http_chunks.c */ +void Curl_httpchunk_init(struct Curl_easy *data); +CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, char *buf, + size_t blen, size_t *pconsumed, + CURLcode *passthru); + +#endif /* HEADER_CURL_HTTP_CHUNKS_H */ diff --git a/Utilities/cmcurl/lib/http_digest.c b/Utilities/cmcurl/lib/http_digest.c new file mode 100644 index 0000000..2db3125 --- /dev/null +++ b/Utilities/cmcurl/lib/http_digest.c @@ -0,0 +1,185 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) + +#include "urldata.h" +#include "strcase.h" +#include "vauth/vauth.h" +#include "http_digest.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* Test example headers: + +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: + header */ +{ + /* Point to the correct struct with this */ + struct digestdata *digest; + + if(proxy) { + digest = &data->state.proxydigest; + } + else { + digest = &data->state.digest; + } + + if(!checkprefix("Digest", header) || !ISBLANK(header[6])) + return CURLE_BAD_CONTENT_ENCODING; + + header += strlen("Digest"); + while(*header && ISBLANK(*header)) + header++; + + return Curl_auth_decode_digest_http_message(header, digest); +} + +CURLcode Curl_output_digest(struct Curl_easy *data, + bool proxy, + const unsigned char *request, + const unsigned char *uripath) +{ + CURLcode result; + unsigned char *path = NULL; + char *tmp = NULL; + char *response; + size_t len; + bool have_chlg; + + /* Point to the address of the pointer that holds the string to send to the + server, which is for a plain host or for an HTTP proxy */ + char **allocuserpwd; + + /* Point to the name and password for this */ + const char *userp; + const char *passwdp; + + /* Point to the correct struct with this */ + struct digestdata *digest; + struct auth *authp; + + if(proxy) { +#ifdef CURL_DISABLE_PROXY + return CURLE_NOT_BUILT_IN; +#else + digest = &data->state.proxydigest; + allocuserpwd = &data->state.aptr.proxyuserpwd; + userp = data->state.aptr.proxyuser; + passwdp = data->state.aptr.proxypasswd; + authp = &data->state.authproxy; +#endif + } + else { + digest = &data->state.digest; + allocuserpwd = &data->state.aptr.userpwd; + userp = data->state.aptr.user; + passwdp = data->state.aptr.passwd; + authp = &data->state.authhost; + } + + Curl_safefree(*allocuserpwd); + + /* not set means empty */ + if(!userp) + userp = ""; + + if(!passwdp) + passwdp = ""; + +#if defined(USE_WINDOWS_SSPI) + have_chlg = digest->input_token ? TRUE : FALSE; +#else + have_chlg = digest->nonce ? TRUE : FALSE; +#endif + + if(!have_chlg) { + authp->done = FALSE; + return CURLE_OK; + } + + /* So IE browsers < v7 cut off the URI part at the query part when they + evaluate the MD5 and some (IIS?) servers work with them so we may need to + do the Digest IE-style. Note that the different ways cause different MD5 + sums to get sent. + + Apache servers can be set to do the Digest IE-style automatically using + the BrowserMatch feature: + https://httpd.apache.org/docs/2.2/mod/mod_auth_digest.html#msie + + Further details on Digest implementation differences: + http://www.fngtps.com/2006/09/http-authentication + */ + + if(authp->iestyle) { + tmp = strchr((char *)uripath, '?'); + if(tmp) { + size_t urilen = tmp - (char *)uripath; + /* typecast is fine here since the value is always less than 32 bits */ + path = (unsigned char *) aprintf("%.*s", (int)urilen, uripath); + } + } + if(!tmp) + path = (unsigned char *) strdup((char *) uripath); + + if(!path) + return CURLE_OUT_OF_MEMORY; + + result = Curl_auth_create_digest_http_message(data, userp, passwdp, request, + path, digest, &response, &len); + free(path); + if(result) + return result; + + *allocuserpwd = aprintf("%sAuthorization: Digest %s\r\n", + proxy ? "Proxy-" : "", + response); + free(response); + if(!*allocuserpwd) + return CURLE_OUT_OF_MEMORY; + + authp->done = TRUE; + + return CURLE_OK; +} + +void Curl_http_auth_cleanup_digest(struct Curl_easy *data) +{ + Curl_auth_digest_cleanup(&data->state.digest); + Curl_auth_digest_cleanup(&data->state.proxydigest); +} + +#endif diff --git a/Utilities/cmcurl/lib/http_digest.h b/Utilities/cmcurl/lib/http_digest.h new file mode 100644 index 0000000..5f79731 --- /dev/null +++ b/Utilities/cmcurl/lib/http_digest.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_HTTP_DIGEST_H +#define HEADER_CURL_HTTP_DIGEST_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) + +/* this is for digest header input */ +CURLcode Curl_input_digest(struct Curl_easy *data, + bool proxy, const char *header); + +/* this is for creating digest header output */ +CURLcode Curl_output_digest(struct Curl_easy *data, + bool proxy, + const unsigned char *request, + const unsigned char *uripath); + +void Curl_http_auth_cleanup_digest(struct Curl_easy *data); + +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_DIGEST_AUTH */ + +#endif /* HEADER_CURL_HTTP_DIGEST_H */ diff --git a/Utilities/cmcurl/lib/http_negotiate.c b/Utilities/cmcurl/lib/http_negotiate.c new file mode 100644 index 0000000..153e3d4 --- /dev/null +++ b/Utilities/cmcurl/lib/http_negotiate.c @@ -0,0 +1,224 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_SPNEGO) + +#include "urldata.h" +#include "sendf.h" +#include "http_negotiate.h" +#include "vauth/vauth.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, + bool proxy, const char *header) +{ + CURLcode result; + size_t len; + + /* Point to the username, password, service and host */ + const char *userp; + const char *passwdp; + const char *service; + const char *host; + + /* Point to the correct struct with this */ + struct negotiatedata *neg_ctx; + curlnegotiate state; + + if(proxy) { +#ifndef CURL_DISABLE_PROXY + userp = conn->http_proxy.user; + passwdp = conn->http_proxy.passwd; + service = data->set.str[STRING_PROXY_SERVICE_NAME] ? + data->set.str[STRING_PROXY_SERVICE_NAME] : "HTTP"; + host = conn->http_proxy.host.name; + neg_ctx = &conn->proxyneg; + state = conn->proxy_negotiate_state; +#else + return CURLE_NOT_BUILT_IN; +#endif + } + else { + userp = conn->user; + passwdp = conn->passwd; + service = data->set.str[STRING_SERVICE_NAME] ? + data->set.str[STRING_SERVICE_NAME] : "HTTP"; + host = conn->host.name; + neg_ctx = &conn->negotiate; + state = conn->http_negotiate_state; + } + + /* Not set means empty */ + if(!userp) + userp = ""; + + if(!passwdp) + passwdp = ""; + + /* Obtain the input token, if any */ + header += strlen("Negotiate"); + while(*header && ISBLANK(*header)) + header++; + + len = strlen(header); + neg_ctx->havenegdata = len != 0; + if(!len) { + if(state == GSS_AUTHSUCC) { + infof(data, "Negotiate auth restarted"); + Curl_http_auth_cleanup_negotiate(conn); + } + else if(state != GSS_AUTHNONE) { + /* The server rejected our authentication and hasn't supplied any more + negotiation mechanisms */ + Curl_http_auth_cleanup_negotiate(conn); + return CURLE_LOGIN_DENIED; + } + } + + /* Supports SSL channel binding for Windows ISS extended protection */ +#if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) + neg_ctx->sslContext = conn->sslContext; +#endif + + /* Initialize the security context and decode our challenge */ + result = Curl_auth_decode_spnego_message(data, userp, passwdp, service, + host, header, neg_ctx); + + if(result) + Curl_http_auth_cleanup_negotiate(conn); + + return result; +} + +CURLcode Curl_output_negotiate(struct Curl_easy *data, + struct connectdata *conn, bool proxy) +{ + struct negotiatedata *neg_ctx = proxy ? &conn->proxyneg : + &conn->negotiate; + struct auth *authp = proxy ? &data->state.authproxy : &data->state.authhost; + curlnegotiate *state = proxy ? &conn->proxy_negotiate_state : + &conn->http_negotiate_state; + char *base64 = NULL; + size_t len = 0; + char *userp; + CURLcode result; + + authp->done = FALSE; + + if(*state == GSS_AUTHRECV) { + if(neg_ctx->havenegdata) { + neg_ctx->havemultiplerequests = TRUE; + } + } + else if(*state == GSS_AUTHSUCC) { + if(!neg_ctx->havenoauthpersist) { + neg_ctx->noauthpersist = !neg_ctx->havemultiplerequests; + } + } + + if(neg_ctx->noauthpersist || + (*state != GSS_AUTHDONE && *state != GSS_AUTHSUCC)) { + + if(neg_ctx->noauthpersist && *state == GSS_AUTHSUCC) { + infof(data, "Curl_output_negotiate, " + "no persistent authentication: cleanup existing context"); + Curl_http_auth_cleanup_negotiate(conn); + } + if(!neg_ctx->context) { + result = Curl_input_negotiate(data, conn, proxy, "Negotiate"); + if(result == CURLE_AUTH_ERROR) { + /* negotiate auth failed, let's continue unauthenticated to stay + * compatible with the behavior before curl-7_64_0-158-g6c6035532 */ + authp->done = TRUE; + return CURLE_OK; + } + else if(result) + return result; + } + + result = Curl_auth_create_spnego_message(neg_ctx, &base64, &len); + if(result) + return result; + + userp = aprintf("%sAuthorization: Negotiate %s\r\n", proxy ? "Proxy-" : "", + base64); + + if(proxy) { + Curl_safefree(data->state.aptr.proxyuserpwd); + data->state.aptr.proxyuserpwd = userp; + } + else { + Curl_safefree(data->state.aptr.userpwd); + data->state.aptr.userpwd = userp; + } + + free(base64); + + if(!userp) { + return CURLE_OUT_OF_MEMORY; + } + + *state = GSS_AUTHSENT; + #ifdef HAVE_GSSAPI + if(neg_ctx->status == GSS_S_COMPLETE || + neg_ctx->status == GSS_S_CONTINUE_NEEDED) { + *state = GSS_AUTHDONE; + } + #else + #ifdef USE_WINDOWS_SSPI + if(neg_ctx->status == SEC_E_OK || + neg_ctx->status == SEC_I_CONTINUE_NEEDED) { + *state = GSS_AUTHDONE; + } + #endif + #endif + } + + if(*state == GSS_AUTHDONE || *state == GSS_AUTHSUCC) { + /* connection is already authenticated, + * don't send a header in future requests */ + authp->done = TRUE; + } + + neg_ctx->havenegdata = FALSE; + + return CURLE_OK; +} + +void Curl_http_auth_cleanup_negotiate(struct connectdata *conn) +{ + conn->http_negotiate_state = GSS_AUTHNONE; + conn->proxy_negotiate_state = GSS_AUTHNONE; + + Curl_auth_cleanup_spnego(&conn->negotiate); + Curl_auth_cleanup_spnego(&conn->proxyneg); +} + +#endif /* !CURL_DISABLE_HTTP && USE_SPNEGO */ diff --git a/Utilities/cmcurl/lib/http_negotiate.h b/Utilities/cmcurl/lib/http_negotiate.h new file mode 100644 index 0000000..76d8356 --- /dev/null +++ b/Utilities/cmcurl/lib/http_negotiate.h @@ -0,0 +1,43 @@ +#ifndef HEADER_CURL_HTTP_NEGOTIATE_H +#define HEADER_CURL_HTTP_NEGOTIATE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_SPNEGO) + +/* this is for Negotiate header input */ +CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, + bool proxy, const char *header); + +/* this is for creating Negotiate header output */ +CURLcode Curl_output_negotiate(struct Curl_easy *data, + struct connectdata *conn, bool proxy); + +void Curl_http_auth_cleanup_negotiate(struct connectdata *conn); + +#else /* !CURL_DISABLE_HTTP && USE_SPNEGO */ +#define Curl_http_auth_cleanup_negotiate(x) +#endif + +#endif /* HEADER_CURL_HTTP_NEGOTIATE_H */ diff --git a/Utilities/cmcurl/lib/http_ntlm.c b/Utilities/cmcurl/lib/http_ntlm.c new file mode 100644 index 0000000..b845ddf --- /dev/null +++ b/Utilities/cmcurl/lib/http_ntlm.c @@ -0,0 +1,275 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) + +/* + * NTLM details: + * + * https://davenport.sourceforge.net/ntlm.html + * https://www.innovation.ch/java/ntlm.html + */ + +#define DEBUG_ME 0 + +#include "urldata.h" +#include "sendf.h" +#include "strcase.h" +#include "http_ntlm.h" +#include "curl_ntlm_core.h" +#include "curl_ntlm_wb.h" +#include "curl_base64.h" +#include "vauth/vauth.h" +#include "url.h" + +/* SSL backend-specific #if branches in this file must be kept in the order + documented in curl_ntlm_core. */ +#if defined(USE_WINDOWS_SSPI) +#include "curl_sspi.h" +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#if DEBUG_ME +# define DEBUG_OUT(x) x +#else +# define DEBUG_OUT(x) Curl_nop_stmt +#endif + +CURLcode Curl_input_ntlm(struct Curl_easy *data, + bool proxy, /* if proxy or not */ + const char *header) /* rest of the www-authenticate: + header */ +{ + /* point to the correct struct with this */ + struct ntlmdata *ntlm; + curlntlm *state; + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + ntlm = proxy ? &conn->proxyntlm : &conn->ntlm; + state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state; + + if(checkprefix("NTLM", header)) { + header += strlen("NTLM"); + + while(*header && ISSPACE(*header)) + header++; + + if(*header) { + unsigned char *hdr; + size_t hdrlen; + + result = Curl_base64_decode(header, &hdr, &hdrlen); + if(!result) { + struct bufref hdrbuf; + + Curl_bufref_init(&hdrbuf); + Curl_bufref_set(&hdrbuf, hdr, hdrlen, curl_free); + result = Curl_auth_decode_ntlm_type2_message(data, &hdrbuf, ntlm); + Curl_bufref_free(&hdrbuf); + } + if(result) + return result; + + *state = NTLMSTATE_TYPE2; /* We got a type-2 message */ + } + else { + if(*state == NTLMSTATE_LAST) { + infof(data, "NTLM auth restarted"); + Curl_http_auth_cleanup_ntlm(conn); + } + else if(*state == NTLMSTATE_TYPE3) { + infof(data, "NTLM handshake rejected"); + Curl_http_auth_cleanup_ntlm(conn); + *state = NTLMSTATE_NONE; + return CURLE_REMOTE_ACCESS_DENIED; + } + else if(*state >= NTLMSTATE_TYPE1) { + infof(data, "NTLM handshake failure (internal error)"); + return CURLE_REMOTE_ACCESS_DENIED; + } + + *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */ + } + } + + return result; +} + +/* + * This is for creating ntlm header output + */ +CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) +{ + char *base64 = NULL; + size_t len = 0; + CURLcode result = CURLE_OK; + struct bufref ntlmmsg; + + /* point to the address of the pointer that holds the string to send to the + server, which is for a plain host or for an HTTP proxy */ + char **allocuserpwd; + + /* point to the username, password, service and host */ + const char *userp; + const char *passwdp; + const char *service = NULL; + const char *hostname = NULL; + + /* point to the correct struct with this */ + struct ntlmdata *ntlm; + curlntlm *state; + struct auth *authp; + struct connectdata *conn = data->conn; + + DEBUGASSERT(conn); + DEBUGASSERT(data); + + if(proxy) { +#ifndef CURL_DISABLE_PROXY + allocuserpwd = &data->state.aptr.proxyuserpwd; + userp = data->state.aptr.proxyuser; + passwdp = data->state.aptr.proxypasswd; + service = data->set.str[STRING_PROXY_SERVICE_NAME] ? + data->set.str[STRING_PROXY_SERVICE_NAME] : "HTTP"; + hostname = conn->http_proxy.host.name; + ntlm = &conn->proxyntlm; + state = &conn->proxy_ntlm_state; + authp = &data->state.authproxy; +#else + return CURLE_NOT_BUILT_IN; +#endif + } + else { + allocuserpwd = &data->state.aptr.userpwd; + userp = data->state.aptr.user; + passwdp = data->state.aptr.passwd; + service = data->set.str[STRING_SERVICE_NAME] ? + data->set.str[STRING_SERVICE_NAME] : "HTTP"; + hostname = conn->host.name; + ntlm = &conn->ntlm; + state = &conn->http_ntlm_state; + authp = &data->state.authhost; + } + authp->done = FALSE; + + /* not set means empty */ + if(!userp) + userp = ""; + + if(!passwdp) + passwdp = ""; + +#ifdef USE_WINDOWS_SSPI + if(!s_hSecDll) { + /* not thread safe and leaks - use curl_global_init() to avoid */ + CURLcode err = Curl_sspi_global_init(); + if(!s_hSecDll) + return err; + } +#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS + ntlm->sslContext = conn->sslContext; +#endif +#endif + + Curl_bufref_init(&ntlmmsg); + + /* connection is already authenticated, don't send a header in future + * requests so go directly to NTLMSTATE_LAST */ + if(*state == NTLMSTATE_TYPE3) + *state = NTLMSTATE_LAST; + + switch(*state) { + case NTLMSTATE_TYPE1: + default: /* for the weird cases we (re)start here */ + /* Create a type-1 message */ + result = Curl_auth_create_ntlm_type1_message(data, userp, passwdp, + service, hostname, + ntlm, &ntlmmsg); + if(!result) { + DEBUGASSERT(Curl_bufref_len(&ntlmmsg) != 0); + result = Curl_base64_encode((const char *) Curl_bufref_ptr(&ntlmmsg), + Curl_bufref_len(&ntlmmsg), &base64, &len); + if(!result) { + free(*allocuserpwd); + *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", + proxy ? "Proxy-" : "", + base64); + free(base64); + if(!*allocuserpwd) + result = CURLE_OUT_OF_MEMORY; + } + } + break; + + case NTLMSTATE_TYPE2: + /* We already received the type-2 message, create a type-3 message */ + result = Curl_auth_create_ntlm_type3_message(data, userp, passwdp, + ntlm, &ntlmmsg); + if(!result && Curl_bufref_len(&ntlmmsg)) { + result = Curl_base64_encode((const char *) Curl_bufref_ptr(&ntlmmsg), + Curl_bufref_len(&ntlmmsg), &base64, &len); + if(!result) { + free(*allocuserpwd); + *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", + proxy ? "Proxy-" : "", + base64); + free(base64); + if(!*allocuserpwd) + result = CURLE_OUT_OF_MEMORY; + else { + *state = NTLMSTATE_TYPE3; /* we send a type-3 */ + authp->done = TRUE; + } + } + } + break; + + case NTLMSTATE_LAST: + Curl_safefree(*allocuserpwd); + authp->done = TRUE; + break; + } + Curl_bufref_free(&ntlmmsg); + + return result; +} + +void Curl_http_auth_cleanup_ntlm(struct connectdata *conn) +{ + Curl_auth_cleanup_ntlm(&conn->ntlm); + Curl_auth_cleanup_ntlm(&conn->proxyntlm); + +#if defined(NTLM_WB_ENABLED) + Curl_http_auth_cleanup_ntlm_wb(conn); +#endif +} + +#endif /* !CURL_DISABLE_HTTP && USE_NTLM */ diff --git a/Utilities/cmcurl/lib/http_ntlm.h b/Utilities/cmcurl/lib/http_ntlm.h new file mode 100644 index 0000000..f37572b --- /dev/null +++ b/Utilities/cmcurl/lib/http_ntlm.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_HTTP_NTLM_H +#define HEADER_CURL_HTTP_NTLM_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) + +/* this is for ntlm header input */ +CURLcode Curl_input_ntlm(struct Curl_easy *data, bool proxy, + const char *header); + +/* this is for creating ntlm header output */ +CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy); + +void Curl_http_auth_cleanup_ntlm(struct connectdata *conn); + +#else /* !CURL_DISABLE_HTTP && USE_NTLM */ +#define Curl_http_auth_cleanup_ntlm(x) +#endif + +#endif /* HEADER_CURL_HTTP_NTLM_H */ diff --git a/Utilities/cmcurl/lib/http_proxy.c b/Utilities/cmcurl/lib/http_proxy.c new file mode 100644 index 0000000..8e18325 --- /dev/null +++ b/Utilities/cmcurl/lib/http_proxy.c @@ -0,0 +1,336 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "http_proxy.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY) + +#include <curl/curl.h> +#ifdef USE_HYPER +#include <hyper.h> +#endif +#include "sendf.h" +#include "http.h" +#include "url.h" +#include "select.h" +#include "progress.h" +#include "cfilters.h" +#include "cf-h1-proxy.h" +#include "cf-h2-proxy.h" +#include "connect.h" +#include "curlx.h" +#include "vtls/vtls.h" +#include "transfer.h" +#include "multiif.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf, + const char **phostname, + int *pport, bool *pipv6_ip) +{ + DEBUGASSERT(cf); + DEBUGASSERT(cf->conn); + + if(cf->conn->bits.conn_to_host) + *phostname = cf->conn->conn_to_host.name; + else if(cf->sockindex == SECONDARYSOCKET) + *phostname = cf->conn->secondaryhostname; + else + *phostname = cf->conn->host.name; + + if(cf->sockindex == SECONDARYSOCKET) + *pport = cf->conn->secondary_port; + else if(cf->conn->bits.conn_to_port) + *pport = cf->conn->conn_to_port; + else + *pport = cf->conn->remote_port; + + if(*phostname != cf->conn->host.name) + *pipv6_ip = (strchr(*phostname, ':') != NULL); + else + *pipv6_ip = cf->conn->bits.ipv6_ip; + + return CURLE_OK; +} + +CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, + struct Curl_cfilter *cf, + struct Curl_easy *data, + int http_version_major) +{ + const char *hostname = NULL; + char *authority = NULL; + int port; + bool ipv6_ip; + CURLcode result; + struct httpreq *req = NULL; + + result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); + if(result) + goto out; + + authority = aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, + ipv6_ip?"]":"", port); + if(!authority) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1, + NULL, 0, authority, strlen(authority), + NULL, 0); + if(result) + goto out; + + /* Setup the proxy-authorization header, if any */ + result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET, + req->authority, TRUE); + if(result) + goto out; + + /* If user is not overriding Host: header, we add for HTTP/1.x */ + if(http_version_major == 1 && + !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) { + result = Curl_dynhds_cadd(&req->headers, "Host", authority); + if(result) + goto out; + } + + if(data->state.aptr.proxyuserpwd) { + result = Curl_dynhds_h1_cadd_line(&req->headers, + data->state.aptr.proxyuserpwd); + if(result) + goto out; + } + + if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) + && data->set.str[STRING_USERAGENT]) { + result = Curl_dynhds_cadd(&req->headers, "User-Agent", + data->set.str[STRING_USERAGENT]); + if(result) + goto out; + } + + if(http_version_major == 1 && + !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) { + result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive"); + if(result) + goto out; + } + + result = Curl_dynhds_add_custom(data, TRUE, &req->headers); + +out: + if(result && req) { + Curl_http_req_free(req); + req = NULL; + } + free(authority); + *preq = req; + return result; +} + + +struct cf_proxy_ctx { + /* the protocol specific sub-filter we install during connect */ + struct Curl_cfilter *cf_protocol; +}; + +static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_proxy_ctx *ctx = cf->ctx; + CURLcode result; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + CURL_TRC_CF(data, cf, "connect"); +connect_sub: + result = cf->next->cft->do_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + + *done = FALSE; + if(!ctx->cf_protocol) { + struct Curl_cfilter *cf_protocol = NULL; + int alpn = Curl_conn_cf_is_ssl(cf->next)? + cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1; + + /* First time call after the subchain connected */ + switch(alpn) { + case CURL_HTTP_VERSION_NONE: + case CURL_HTTP_VERSION_1_0: + case CURL_HTTP_VERSION_1_1: + CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.1"); + infof(data, "CONNECT tunnel: HTTP/1.%d negotiated", + (alpn == CURL_HTTP_VERSION_1_0)? 0 : 1); + result = Curl_cf_h1_proxy_insert_after(cf, data); + if(result) + goto out; + cf_protocol = cf->next; + break; +#ifdef USE_NGHTTP2 + case CURL_HTTP_VERSION_2: + CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2"); + infof(data, "CONNECT tunnel: HTTP/2 negotiated"); + result = Curl_cf_h2_proxy_insert_after(cf, data); + if(result) + goto out; + cf_protocol = cf->next; + break; +#endif + default: + infof(data, "CONNECT tunnel: unsupported ALPN(%d) negotiated", alpn); + result = CURLE_COULDNT_CONNECT; + goto out; + } + + ctx->cf_protocol = cf_protocol; + /* after we installed the filter "below" us, we call connect + * on out sub-chain again. + */ + goto connect_sub; + } + else { + /* subchain connected and we had already installed the protocol filter. + * This means the protocol tunnel is established, we are done. + */ + DEBUGASSERT(ctx->cf_protocol); + result = CURLE_OK; + } + +out: + if(!result) { + cf->connected = TRUE; + *done = TRUE; + } + return result; +} + +void Curl_cf_http_proxy_get_host(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char **phost, + const char **pdisplay_host, + int *pport) +{ + (void)data; + if(!cf->connected) { + *phost = cf->conn->http_proxy.host.name; + *pdisplay_host = cf->conn->http_proxy.host.dispname; + *pport = (int)cf->conn->http_proxy.port; + } + else { + cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport); + } +} + +static void http_proxy_cf_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_proxy_ctx *ctx = cf->ctx; + + (void)data; + CURL_TRC_CF(data, cf, "destroy"); + free(ctx); +} + +static void http_proxy_cf_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_proxy_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "close"); + cf->connected = FALSE; + if(ctx->cf_protocol) { + struct Curl_cfilter *f; + /* if someone already removed it, we assume he also + * took care of destroying it. */ + for(f = cf->next; f; f = f->next) { + if(f == ctx->cf_protocol) { + /* still in our sub-chain */ + Curl_conn_cf_discard_sub(cf, ctx->cf_protocol, data, FALSE); + break; + } + } + ctx->cf_protocol = NULL; + } + if(cf->next) + cf->next->cft->do_close(cf->next, data); +} + + +struct Curl_cftype Curl_cft_http_proxy = { + "HTTP-PROXY", + CF_TYPE_IP_CONNECT, + 0, + http_proxy_cf_destroy, + http_proxy_cf_connect, + http_proxy_cf_close, + Curl_cf_http_proxy_get_host, + Curl_cf_def_adjust_pollset, + Curl_cf_def_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + struct cf_proxy_ctx *ctx = NULL; + CURLcode result; + + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx); + if(result) + goto out; + ctx = NULL; + Curl_conn_cf_insert_after(cf_at, cf); + +out: + free(ctx); + return result; +} + +#endif /* ! CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */ diff --git a/Utilities/cmcurl/lib/http_proxy.h b/Utilities/cmcurl/lib/http_proxy.h new file mode 100644 index 0000000..2b5f7ae --- /dev/null +++ b/Utilities/cmcurl/lib/http_proxy.h @@ -0,0 +1,61 @@ +#ifndef HEADER_CURL_HTTP_PROXY_H +#define HEADER_CURL_HTTP_PROXY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) + +#include "urldata.h" + +CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf, + const char **phostname, + int *pport, bool *pipv6_ip); + +CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, + struct Curl_cfilter *cf, + struct Curl_easy *data, + int http_version_major); + +/* Default proxy timeout in milliseconds */ +#define PROXY_TIMEOUT (3600*1000) + +void Curl_cf_http_proxy_get_host(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char **phost, + const char **pdisplay_host, + int *pport); + +CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data); + +extern struct Curl_cftype Curl_cft_http_proxy; + +#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ + +#define IS_HTTPS_PROXY(t) (((t) == CURLPROXY_HTTPS) || \ + ((t) == CURLPROXY_HTTPS2)) + +#endif /* HEADER_CURL_HTTP_PROXY_H */ diff --git a/Utilities/cmcurl/lib/idn.c b/Utilities/cmcurl/lib/idn.c new file mode 100644 index 0000000..81a177f --- /dev/null +++ b/Utilities/cmcurl/lib/idn.c @@ -0,0 +1,287 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + + /* + * IDN conversions + */ + +#include "curl_setup.h" +#include "urldata.h" +#include "idn.h" +#include "sendf.h" +#include "curl_multibyte.h" +#include "warnless.h" + +#ifdef USE_LIBIDN2 +#include <idn2.h> + +#if defined(_WIN32) && defined(UNICODE) +#define IDN2_LOOKUP(name, host, flags) \ + idn2_lookup_u8((const uint8_t *)name, (uint8_t **)host, flags) +#else +#define IDN2_LOOKUP(name, host, flags) \ + idn2_lookup_ul((const char *)name, (char **)host, flags) +#endif +#endif /* USE_LIBIDN2 */ + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef USE_WIN32_IDN +/* using Windows kernel32 and normaliz libraries. */ + +#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x600 +WINBASEAPI int WINAPI IdnToAscii(DWORD dwFlags, + const WCHAR *lpUnicodeCharStr, + int cchUnicodeChar, + WCHAR *lpASCIICharStr, + int cchASCIIChar); +WINBASEAPI int WINAPI IdnToUnicode(DWORD dwFlags, + const WCHAR *lpASCIICharStr, + int cchASCIIChar, + WCHAR *lpUnicodeCharStr, + int cchUnicodeChar); +#endif + +#define IDN_MAX_LENGTH 255 + +static CURLcode win32_idn_to_ascii(const char *in, char **out) +{ + wchar_t *in_w = curlx_convert_UTF8_to_wchar(in); + *out = NULL; + if(in_w) { + wchar_t punycode[IDN_MAX_LENGTH]; + int chars = IdnToAscii(0, in_w, (int)(wcslen(in_w) + 1), punycode, + IDN_MAX_LENGTH); + curlx_unicodefree(in_w); + if(chars) { + char *mstr = curlx_convert_wchar_to_UTF8(punycode); + if(mstr) { + *out = strdup(mstr); + curlx_unicodefree(mstr); + if(!*out) + return CURLE_OUT_OF_MEMORY; + } + else + return CURLE_OUT_OF_MEMORY; + } + else + return CURLE_URL_MALFORMAT; + } + else + return CURLE_URL_MALFORMAT; + + return CURLE_OK; +} + +static CURLcode win32_ascii_to_idn(const char *in, char **output) +{ + char *out = NULL; + + wchar_t *in_w = curlx_convert_UTF8_to_wchar(in); + if(in_w) { + WCHAR idn[IDN_MAX_LENGTH]; /* stores a UTF-16 string */ + int chars = IdnToUnicode(0, in_w, (int)(wcslen(in_w) + 1), idn, + IDN_MAX_LENGTH); + if(chars) { + /* 'chars' is "the number of characters retrieved" */ + char *mstr = curlx_convert_wchar_to_UTF8(idn); + if(mstr) { + out = strdup(mstr); + curlx_unicodefree(mstr); + if(!out) + return CURLE_OUT_OF_MEMORY; + } + } + else + return CURLE_URL_MALFORMAT; + } + else + return CURLE_URL_MALFORMAT; + *output = out; + return CURLE_OK; +} + +#endif /* USE_WIN32_IDN */ + +/* + * Helpers for IDNA conversions. + */ +bool Curl_is_ASCII_name(const char *hostname) +{ + /* get an UNSIGNED local version of the pointer */ + const unsigned char *ch = (const unsigned char *)hostname; + + if(!hostname) /* bad input, consider it ASCII! */ + return TRUE; + + while(*ch) { + if(*ch++ & 0x80) + return FALSE; + } + return TRUE; +} + +#ifdef USE_IDN +/* + * Curl_idn_decode() returns an allocated IDN decoded string if it was + * possible. NULL on error. + * + * CURLE_URL_MALFORMAT - the host name could not be converted + * CURLE_OUT_OF_MEMORY - memory problem + * + */ +static CURLcode idn_decode(const char *input, char **output) +{ + char *decoded = NULL; + CURLcode result = CURLE_OK; +#ifdef USE_LIBIDN2 + if(idn2_check_version(IDN2_VERSION)) { + int flags = IDN2_NFC_INPUT +#if IDN2_VERSION_NUMBER >= 0x00140000 + /* IDN2_NFC_INPUT: Normalize input string using normalization form C. + IDN2_NONTRANSITIONAL: Perform Unicode TR46 non-transitional + processing. */ + | IDN2_NONTRANSITIONAL +#endif + ; + int rc = IDN2_LOOKUP(input, &decoded, flags); + if(rc != IDN2_OK) + /* fallback to TR46 Transitional mode for better IDNA2003 + compatibility */ + rc = IDN2_LOOKUP(input, &decoded, IDN2_TRANSITIONAL); + if(rc != IDN2_OK) + result = CURLE_URL_MALFORMAT; + } + else + /* a too old libidn2 version */ + result = CURLE_NOT_BUILT_IN; +#elif defined(USE_WIN32_IDN) + result = win32_idn_to_ascii(input, &decoded); +#endif + if(!result) + *output = decoded; + return result; +} + +static CURLcode idn_encode(const char *puny, char **output) +{ + char *enc = NULL; +#ifdef USE_LIBIDN2 + int rc = idn2_to_unicode_8z8z(puny, &enc, 0); + if(rc != IDNA_SUCCESS) + return rc == IDNA_MALLOC_ERROR ? CURLE_OUT_OF_MEMORY : CURLE_URL_MALFORMAT; +#elif defined(USE_WIN32_IDN) + CURLcode result = win32_ascii_to_idn(puny, &enc); + if(result) + return result; +#endif + *output = enc; + return CURLE_OK; +} + +CURLcode Curl_idn_decode(const char *input, char **output) +{ + char *d = NULL; + CURLcode result = idn_decode(input, &d); +#ifdef USE_LIBIDN2 + if(!result) { + char *c = strdup(d); + idn2_free(d); + if(c) + d = c; + else + result = CURLE_OUT_OF_MEMORY; + } +#endif + if(!result) + *output = d; + return result; +} + +CURLcode Curl_idn_encode(const char *puny, char **output) +{ + char *d = NULL; + CURLcode result = idn_encode(puny, &d); +#ifdef USE_LIBIDN2 + if(!result) { + char *c = strdup(d); + idn2_free(d); + if(c) + d = c; + else + result = CURLE_OUT_OF_MEMORY; + } +#endif + if(!result) + *output = d; + return result; +} + +/* + * Frees data allocated by idnconvert_hostname() + */ +void Curl_free_idnconverted_hostname(struct hostname *host) +{ + if(host->encalloc) { + /* must be freed with idn2_free() if allocated by libidn */ + Curl_idn_free(host->encalloc); + host->encalloc = NULL; + } +} + +#endif /* USE_IDN */ + +/* + * Perform any necessary IDN conversion of hostname + */ +CURLcode Curl_idnconvert_hostname(struct hostname *host) +{ + /* set the name we use to display the host name */ + host->dispname = host->name; + +#ifdef USE_IDN + /* Check name for non-ASCII and convert hostname if we can */ + if(!Curl_is_ASCII_name(host->name)) { + char *decoded; + CURLcode result = idn_decode(host->name, &decoded); + if(!result) { + if(!*decoded) { + /* zero length is a bad host name */ + Curl_idn_free(decoded); + return CURLE_URL_MALFORMAT; + } + /* successful */ + host->encalloc = decoded; + /* change the name pointer to point to the encoded hostname */ + host->name = host->encalloc; + } + else + return result; + } +#endif + return CURLE_OK; +} diff --git a/Utilities/cmcurl/lib/idn.h b/Utilities/cmcurl/lib/idn.h new file mode 100644 index 0000000..74bbcaf --- /dev/null +++ b/Utilities/cmcurl/lib/idn.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_IDN_H +#define HEADER_CURL_IDN_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +bool Curl_is_ASCII_name(const char *hostname); +CURLcode Curl_idnconvert_hostname(struct hostname *host); +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) +#define USE_IDN +void Curl_free_idnconverted_hostname(struct hostname *host); +CURLcode Curl_idn_decode(const char *input, char **output); +CURLcode Curl_idn_encode(const char *input, char **output); +#ifdef USE_LIBIDN2 +#define Curl_idn_free(x) idn2_free(x) +#else +#define Curl_idn_free(x) free(x) +#endif + +#else +#define Curl_free_idnconverted_hostname(x) +#define Curl_idn_decode(x) NULL +#endif +#endif /* HEADER_CURL_IDN_H */ diff --git a/Utilities/cmcurl/lib/if2ip.c b/Utilities/cmcurl/lib/if2ip.c new file mode 100644 index 0000000..5249f6c --- /dev/null +++ b/Utilities/cmcurl/lib/if2ip.c @@ -0,0 +1,260 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif +#ifdef HAVE_NET_IF_H +# include <net/if.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif +#ifdef HAVE_NETDB_H +# include <netdb.h> +#endif +#ifdef HAVE_SYS_SOCKIO_H +# include <sys/sockio.h> +#endif +#ifdef HAVE_IFADDRS_H +# include <ifaddrs.h> +#endif +#ifdef HAVE_STROPTS_H +# include <stropts.h> +#endif +#ifdef __VMS +# include <inet.h> +#endif + +#include "inet_ntop.h" +#include "strcase.h" +#include "if2ip.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* ------------------------------------------------------------------ */ + +#ifdef ENABLE_IPV6 +/* Return the scope of the given address. */ +unsigned int Curl_ipv6_scope(const struct sockaddr *sa) +{ + if(sa->sa_family == AF_INET6) { + const struct sockaddr_in6 * sa6 = (const struct sockaddr_in6 *)(void *) sa; + const unsigned char *b = sa6->sin6_addr.s6_addr; + unsigned short w = (unsigned short) ((b[0] << 8) | b[1]); + + if((b[0] & 0xFE) == 0xFC) /* Handle ULAs */ + return IPV6_SCOPE_UNIQUELOCAL; + switch(w & 0xFFC0) { + case 0xFE80: + return IPV6_SCOPE_LINKLOCAL; + case 0xFEC0: + return IPV6_SCOPE_SITELOCAL; + case 0x0000: + w = b[1] | b[2] | b[3] | b[4] | b[5] | b[6] | b[7] | b[8] | b[9] | + b[10] | b[11] | b[12] | b[13] | b[14]; + if(w || b[15] != 0x01) + break; + return IPV6_SCOPE_NODELOCAL; + default: + break; + } + } + return IPV6_SCOPE_GLOBAL; +} +#endif + +#ifndef CURL_DISABLE_BINDLOCAL + +#if defined(HAVE_GETIFADDRS) + +if2ip_result_t Curl_if2ip(int af, +#ifdef ENABLE_IPV6 + unsigned int remote_scope, + unsigned int local_scope_id, +#endif + const char *interf, + char *buf, int buf_size) +{ + struct ifaddrs *iface, *head; + if2ip_result_t res = IF2IP_NOT_FOUND; + +#if defined(ENABLE_IPV6) && \ + !defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) + (void) local_scope_id; +#endif + + if(getifaddrs(&head) >= 0) { + for(iface = head; iface != NULL; iface = iface->ifa_next) { + if(iface->ifa_addr) { + if(iface->ifa_addr->sa_family == af) { + if(strcasecompare(iface->ifa_name, interf)) { + void *addr; + const char *ip; + char scope[12] = ""; + char ipstr[64]; +#ifdef ENABLE_IPV6 + if(af == AF_INET6) { +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + unsigned int scopeid = 0; +#endif + unsigned int ifscope = Curl_ipv6_scope(iface->ifa_addr); + + if(ifscope != remote_scope) { + /* We are interested only in interface addresses whose scope + matches the remote address we want to connect to: global + for global, link-local for link-local, etc... */ + if(res == IF2IP_NOT_FOUND) + res = IF2IP_AF_NOT_SUPPORTED; + continue; + } + + addr = + &((struct sockaddr_in6 *)(void *)iface->ifa_addr)->sin6_addr; +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + /* Include the scope of this interface as part of the address */ + scopeid = ((struct sockaddr_in6 *)(void *)iface->ifa_addr) + ->sin6_scope_id; + + /* If given, scope id should match. */ + if(local_scope_id && scopeid != local_scope_id) { + if(res == IF2IP_NOT_FOUND) + res = IF2IP_AF_NOT_SUPPORTED; + + continue; + } + + if(scopeid) + msnprintf(scope, sizeof(scope), "%%%u", scopeid); +#endif + } + else +#endif + addr = + &((struct sockaddr_in *)(void *)iface->ifa_addr)->sin_addr; + res = IF2IP_FOUND; + ip = Curl_inet_ntop(af, addr, ipstr, sizeof(ipstr)); + msnprintf(buf, buf_size, "%s%s", ip, scope); + break; + } + } + else if((res == IF2IP_NOT_FOUND) && + strcasecompare(iface->ifa_name, interf)) { + res = IF2IP_AF_NOT_SUPPORTED; + } + } + } + + freeifaddrs(head); + } + + return res; +} + +#elif defined(HAVE_IOCTL_SIOCGIFADDR) + +if2ip_result_t Curl_if2ip(int af, +#ifdef ENABLE_IPV6 + unsigned int remote_scope, + unsigned int local_scope_id, +#endif + const char *interf, + char *buf, int buf_size) +{ + struct ifreq req; + struct in_addr in; + struct sockaddr_in *s; + curl_socket_t dummy; + size_t len; + const char *r; + +#ifdef ENABLE_IPV6 + (void)remote_scope; + (void)local_scope_id; +#endif + + if(!interf || (af != AF_INET)) + return IF2IP_NOT_FOUND; + + len = strlen(interf); + if(len >= sizeof(req.ifr_name)) + return IF2IP_NOT_FOUND; + + dummy = socket(AF_INET, SOCK_STREAM, 0); + if(CURL_SOCKET_BAD == dummy) + return IF2IP_NOT_FOUND; + + memset(&req, 0, sizeof(req)); + memcpy(req.ifr_name, interf, len + 1); + req.ifr_addr.sa_family = AF_INET; + + if(ioctl(dummy, SIOCGIFADDR, &req) < 0) { + sclose(dummy); + /* With SIOCGIFADDR, we cannot tell the difference between an interface + that does not exist and an interface that has no address of the + correct family. Assume the interface does not exist */ + return IF2IP_NOT_FOUND; + } + + s = (struct sockaddr_in *)(void *)&req.ifr_addr; + memcpy(&in, &s->sin_addr, sizeof(in)); + r = Curl_inet_ntop(s->sin_family, &in, buf, buf_size); + + sclose(dummy); + if(!r) + return IF2IP_NOT_FOUND; + return IF2IP_FOUND; +} + +#else + +if2ip_result_t Curl_if2ip(int af, +#ifdef ENABLE_IPV6 + unsigned int remote_scope, + unsigned int local_scope_id, +#endif + const char *interf, + char *buf, int buf_size) +{ + (void) af; +#ifdef ENABLE_IPV6 + (void) remote_scope; + (void) local_scope_id; +#endif + (void) interf; + (void) buf; + (void) buf_size; + return IF2IP_NOT_FOUND; +} + +#endif + +#endif /* CURL_DISABLE_BINDLOCAL */ diff --git a/Utilities/cmcurl/lib/if2ip.h b/Utilities/cmcurl/lib/if2ip.h new file mode 100644 index 0000000..1f97350 --- /dev/null +++ b/Utilities/cmcurl/lib/if2ip.h @@ -0,0 +1,92 @@ +#ifndef HEADER_CURL_IF2IP_H +#define HEADER_CURL_IF2IP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +/* IPv6 address scopes. */ +#define IPV6_SCOPE_GLOBAL 0 /* Global scope. */ +#define IPV6_SCOPE_LINKLOCAL 1 /* Link-local scope. */ +#define IPV6_SCOPE_SITELOCAL 2 /* Site-local scope (deprecated). */ +#define IPV6_SCOPE_UNIQUELOCAL 3 /* Unique local */ +#define IPV6_SCOPE_NODELOCAL 4 /* Loopback. */ + +#ifdef ENABLE_IPV6 +unsigned int Curl_ipv6_scope(const struct sockaddr *sa); +#else +#define Curl_ipv6_scope(x) 0 +#endif + +typedef enum { + IF2IP_NOT_FOUND = 0, /* Interface not found */ + IF2IP_AF_NOT_SUPPORTED = 1, /* Int. exists but has no address for this af */ + IF2IP_FOUND = 2 /* The address has been stored in "buf" */ +} if2ip_result_t; + +if2ip_result_t Curl_if2ip(int af, +#ifdef ENABLE_IPV6 + unsigned int remote_scope, + unsigned int local_scope_id, +#endif + const char *interf, + char *buf, int buf_size); + +#ifdef __INTERIX + +/* Nedelcho Stanev's work-around for SFU 3.0 */ +struct ifreq { +#define IFNAMSIZ 16 +#define IFHWADDRLEN 6 + union { + char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */ + } ifr_ifrn; + + union { + struct sockaddr ifru_addr; + struct sockaddr ifru_broadaddr; + struct sockaddr ifru_netmask; + struct sockaddr ifru_hwaddr; + short ifru_flags; + int ifru_metric; + int ifru_mtu; + } ifr_ifru; +}; + +/* This define was added by Daniel to avoid an extra #ifdef INTERIX in the + C code. */ + +#define ifr_name ifr_ifrn.ifrn_name /* interface name */ +#define ifr_addr ifr_ifru.ifru_addr /* address */ +#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ +#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */ +#define ifr_flags ifr_ifru.ifru_flags /* flags */ +#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */ +#define ifr_metric ifr_ifru.ifru_metric /* metric */ +#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */ + +#define SIOCGIFADDR _IOW('s', 102, struct ifreq) /* Get if addr */ + +#endif /* __INTERIX */ + +#endif /* HEADER_CURL_IF2IP_H */ diff --git a/Utilities/cmcurl/lib/imap.c b/Utilities/cmcurl/lib/imap.c new file mode 100644 index 0000000..47cff48 --- /dev/null +++ b/Utilities/cmcurl/lib/imap.c @@ -0,0 +1,2114 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC2195 CRAM-MD5 authentication + * RFC2595 Using TLS with IMAP, POP3 and ACAP + * RFC2831 DIGEST-MD5 authentication + * RFC3501 IMAPv4 protocol + * RFC4422 Simple Authentication and Security Layer (SASL) + * RFC4616 PLAIN authentication + * RFC4752 The Kerberos V5 ("GSSAPI") SASL Mechanism + * RFC4959 IMAP Extension for SASL Initial Client Response + * RFC5092 IMAP URL Scheme + * RFC6749 OAuth 2.0 Authorization Framework + * RFC8314 Use of TLS for Email Submission and Access + * Draft LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt> + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_IMAP + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include <curl/curl.h> +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "progress.h" +#include "transfer.h" +#include "escape.h" +#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "socks.h" +#include "imap.h" +#include "mime.h" +#include "strtoofft.h" +#include "strcase.h" +#include "vtls/vtls.h" +#include "cfilters.h" +#include "connect.h" +#include "select.h" +#include "multiif.h" +#include "url.h" +#include "bufref.h" +#include "curl_sasl.h" +#include "warnless.h" +#include "curl_ctype.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* Local API functions */ +static CURLcode imap_regular_transfer(struct Curl_easy *data, bool *done); +static CURLcode imap_do(struct Curl_easy *data, bool *done); +static CURLcode imap_done(struct Curl_easy *data, CURLcode status, + bool premature); +static CURLcode imap_connect(struct Curl_easy *data, bool *done); +static CURLcode imap_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead); +static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done); +static int imap_getsock(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *socks); +static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done); +static CURLcode imap_setup_connection(struct Curl_easy *data, + struct connectdata *conn); +static char *imap_atom(const char *str, bool escape_only); +static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...); +static CURLcode imap_parse_url_options(struct connectdata *conn); +static CURLcode imap_parse_url_path(struct Curl_easy *data); +static CURLcode imap_parse_custom_request(struct Curl_easy *data); +static CURLcode imap_perform_authenticate(struct Curl_easy *data, + const char *mech, + const struct bufref *initresp); +static CURLcode imap_continue_authenticate(struct Curl_easy *data, + const char *mech, + const struct bufref *resp); +static CURLcode imap_cancel_authenticate(struct Curl_easy *data, + const char *mech); +static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out); + +/* + * IMAP protocol handler. + */ + +const struct Curl_handler Curl_handler_imap = { + "IMAP", /* scheme */ + imap_setup_connection, /* setup_connection */ + imap_do, /* do_it */ + imap_done, /* done */ + ZERO_NULL, /* do_more */ + imap_connect, /* connect_it */ + imap_multi_statemach, /* connecting */ + imap_doing, /* doing */ + imap_getsock, /* proto_getsock */ + imap_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + imap_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_IMAP, /* defport */ + CURLPROTO_IMAP, /* protocol */ + CURLPROTO_IMAP, /* family */ + PROTOPT_CLOSEACTION| /* flags */ + PROTOPT_URLOPTIONS +}; + +#ifdef USE_SSL +/* + * IMAPS protocol handler. + */ + +const struct Curl_handler Curl_handler_imaps = { + "IMAPS", /* scheme */ + imap_setup_connection, /* setup_connection */ + imap_do, /* do_it */ + imap_done, /* done */ + ZERO_NULL, /* do_more */ + imap_connect, /* connect_it */ + imap_multi_statemach, /* connecting */ + imap_doing, /* doing */ + imap_getsock, /* proto_getsock */ + imap_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + imap_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_IMAPS, /* defport */ + CURLPROTO_IMAPS, /* protocol */ + CURLPROTO_IMAP, /* family */ + PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */ + PROTOPT_URLOPTIONS +}; +#endif + +#define IMAP_RESP_OK 1 +#define IMAP_RESP_NOT_OK 2 +#define IMAP_RESP_PREAUTH 3 + +/* SASL parameters for the imap protocol */ +static const struct SASLproto saslimap = { + "imap", /* The service name */ + imap_perform_authenticate, /* Send authentication command */ + imap_continue_authenticate, /* Send authentication continuation */ + imap_cancel_authenticate, /* Send authentication cancellation */ + imap_get_message, /* Get SASL response message */ + 0, /* No maximum initial response length */ + '+', /* Code received when continuation is expected */ + IMAP_RESP_OK, /* Code to receive upon authentication success */ + SASL_AUTH_DEFAULT, /* Default mechanisms */ + SASL_FLAG_BASE64 /* Configuration flags */ +}; + + +#ifdef USE_SSL +static void imap_to_imaps(struct connectdata *conn) +{ + /* Change the connection handler */ + conn->handler = &Curl_handler_imaps; + + /* Set the connection's upgraded to TLS flag */ + conn->bits.tls_upgraded = TRUE; +} +#else +#define imap_to_imaps(x) Curl_nop_stmt +#endif + +/*********************************************************************** + * + * imap_matchresp() + * + * Determines whether the untagged response is related to the specified + * command by checking if it is in format "* <command-name> ..." or + * "* <number> <command-name> ...". + * + * The "* " marker is assumed to have already been checked by the caller. + */ +static bool imap_matchresp(const char *line, size_t len, const char *cmd) +{ + const char *end = line + len; + size_t cmd_len = strlen(cmd); + + /* Skip the untagged response marker */ + line += 2; + + /* Do we have a number after the marker? */ + if(line < end && ISDIGIT(*line)) { + /* Skip the number */ + do + line++; + while(line < end && ISDIGIT(*line)); + + /* Do we have the space character? */ + if(line == end || *line != ' ') + return FALSE; + + line++; + } + + /* Does the command name match and is it followed by a space character or at + the end of line? */ + if(line + cmd_len <= end && strncasecompare(line, cmd, cmd_len) && + (line[cmd_len] == ' ' || line + cmd_len + 2 == end)) + return TRUE; + + return FALSE; +} + +/*********************************************************************** + * + * imap_endofresp() + * + * Checks whether the given string is a valid tagged, untagged or continuation + * response which can be processed by the response handler. + */ +static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn, + char *line, size_t len, int *resp) +{ + struct IMAP *imap = data->req.p.imap; + struct imap_conn *imapc = &conn->proto.imapc; + const char *id = imapc->resptag; + size_t id_len = strlen(id); + + /* Do we have a tagged command response? */ + if(len >= id_len + 1 && !memcmp(id, line, id_len) && line[id_len] == ' ') { + line += id_len + 1; + len -= id_len + 1; + + if(len >= 2 && !memcmp(line, "OK", 2)) + *resp = IMAP_RESP_OK; + else if(len >= 7 && !memcmp(line, "PREAUTH", 7)) + *resp = IMAP_RESP_PREAUTH; + else + *resp = IMAP_RESP_NOT_OK; + + return TRUE; + } + + /* Do we have an untagged command response? */ + if(len >= 2 && !memcmp("* ", line, 2)) { + switch(imapc->state) { + /* States which are interested in untagged responses */ + case IMAP_CAPABILITY: + if(!imap_matchresp(line, len, "CAPABILITY")) + return FALSE; + break; + + case IMAP_LIST: + if((!imap->custom && !imap_matchresp(line, len, "LIST")) || + (imap->custom && !imap_matchresp(line, len, imap->custom) && + (!strcasecompare(imap->custom, "STORE") || + !imap_matchresp(line, len, "FETCH")) && + !strcasecompare(imap->custom, "SELECT") && + !strcasecompare(imap->custom, "EXAMINE") && + !strcasecompare(imap->custom, "SEARCH") && + !strcasecompare(imap->custom, "EXPUNGE") && + !strcasecompare(imap->custom, "LSUB") && + !strcasecompare(imap->custom, "UID") && + !strcasecompare(imap->custom, "GETQUOTAROOT") && + !strcasecompare(imap->custom, "NOOP"))) + return FALSE; + break; + + case IMAP_SELECT: + /* SELECT is special in that its untagged responses do not have a + common prefix so accept anything! */ + break; + + case IMAP_FETCH: + if(!imap_matchresp(line, len, "FETCH")) + return FALSE; + break; + + case IMAP_SEARCH: + if(!imap_matchresp(line, len, "SEARCH")) + return FALSE; + break; + + /* Ignore other untagged responses */ + default: + return FALSE; + } + + *resp = '*'; + return TRUE; + } + + /* Do we have a continuation response? This should be a + symbol followed by + a space and optionally some text as per RFC-3501 for the AUTHENTICATE and + APPEND commands and as outlined in Section 4. Examples of RFC-4959 but + some email servers ignore this and only send a single + instead. */ + if(imap && !imap->custom && ((len == 3 && line[0] == '+') || + (len >= 2 && !memcmp("+ ", line, 2)))) { + switch(imapc->state) { + /* States which are interested in continuation responses */ + case IMAP_AUTHENTICATE: + case IMAP_APPEND: + *resp = '+'; + break; + + default: + failf(data, "Unexpected continuation response"); + *resp = -1; + break; + } + + return TRUE; + } + + return FALSE; /* Nothing for us */ +} + +/*********************************************************************** + * + * imap_get_message() + * + * Gets the authentication message from the response buffer. + */ +static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out) +{ + char *message = data->state.buffer; + size_t len = strlen(message); + + if(len > 2) { + /* Find the start of the message */ + len -= 2; + for(message += 2; *message == ' ' || *message == '\t'; message++, len--) + ; + + /* Find the end of the message */ + while(len--) + if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' && + message[len] != '\t') + break; + + /* Terminate the message */ + message[++len] = '\0'; + Curl_bufref_set(out, message, len, NULL); + } + else + /* junk input => zero length output */ + Curl_bufref_set(out, "", 0, NULL); + + return CURLE_OK; +} + +/*********************************************************************** + * + * imap_state() + * + * This is the ONLY way to change IMAP state! + */ +static void imap_state(struct Curl_easy *data, imapstate newstate) +{ + struct imap_conn *imapc = &data->conn->proto.imapc; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[]={ + "STOP", + "SERVERGREET", + "CAPABILITY", + "STARTTLS", + "UPGRADETLS", + "AUTHENTICATE", + "LOGIN", + "LIST", + "SELECT", + "FETCH", + "FETCH_FINAL", + "APPEND", + "APPEND_FINAL", + "SEARCH", + "LOGOUT", + /* LAST */ + }; + + if(imapc->state != newstate) + infof(data, "IMAP %p state change from %s to %s", + (void *)imapc, names[imapc->state], names[newstate]); +#endif + + imapc->state = newstate; +} + +/*********************************************************************** + * + * imap_perform_capability() + * + * Sends the CAPABILITY command in order to obtain a list of server side + * supported capabilities. + */ +static CURLcode imap_perform_capability(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct imap_conn *imapc = &conn->proto.imapc; + imapc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */ + imapc->sasl.authused = SASL_AUTH_NONE; /* Clear the auth. mechanism used */ + imapc->tls_supported = FALSE; /* Clear the TLS capability */ + + /* Send the CAPABILITY command */ + result = imap_sendf(data, "CAPABILITY"); + + if(!result) + imap_state(data, IMAP_CAPABILITY); + + return result; +} + +/*********************************************************************** + * + * imap_perform_starttls() + * + * Sends the STARTTLS command to start the upgrade to TLS. + */ +static CURLcode imap_perform_starttls(struct Curl_easy *data) +{ + /* Send the STARTTLS command */ + CURLcode result = imap_sendf(data, "STARTTLS"); + + if(!result) + imap_state(data, IMAP_STARTTLS); + + return result; +} + +/*********************************************************************** + * + * imap_perform_upgrade_tls() + * + * Performs the upgrade to TLS. + */ +static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, + struct connectdata *conn) +{ + /* Start the SSL connection */ + struct imap_conn *imapc = &conn->proto.imapc; + CURLcode result; + bool ssldone = FALSE; + + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + if(result) + goto out; + } + + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + if(!result) { + imapc->ssldone = ssldone; + if(imapc->state != IMAP_UPGRADETLS) + imap_state(data, IMAP_UPGRADETLS); + + if(imapc->ssldone) { + imap_to_imaps(conn); + result = imap_perform_capability(data, conn); + } + } +out: + return result; +} + +/*********************************************************************** + * + * imap_perform_login() + * + * Sends a clear text LOGIN command to authenticate with. + */ +static CURLcode imap_perform_login(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + char *user; + char *passwd; + + /* Check we have a username and password to authenticate with and end the + connect phase if we don't */ + if(!data->state.aptr.user) { + imap_state(data, IMAP_STOP); + + return result; + } + + /* Make sure the username and password are in the correct atom format */ + user = imap_atom(conn->user, false); + passwd = imap_atom(conn->passwd, false); + + /* Send the LOGIN command */ + result = imap_sendf(data, "LOGIN %s %s", user ? user : "", + passwd ? passwd : ""); + + free(user); + free(passwd); + + if(!result) + imap_state(data, IMAP_LOGIN); + + return result; +} + +/*********************************************************************** + * + * imap_perform_authenticate() + * + * Sends an AUTHENTICATE command allowing the client to login with the given + * SASL authentication mechanism. + */ +static CURLcode imap_perform_authenticate(struct Curl_easy *data, + const char *mech, + const struct bufref *initresp) +{ + CURLcode result = CURLE_OK; + const char *ir = (const char *) Curl_bufref_ptr(initresp); + + if(ir) { + /* Send the AUTHENTICATE command with the initial response */ + result = imap_sendf(data, "AUTHENTICATE %s %s", mech, ir); + } + else { + /* Send the AUTHENTICATE command */ + result = imap_sendf(data, "AUTHENTICATE %s", mech); + } + + return result; +} + +/*********************************************************************** + * + * imap_continue_authenticate() + * + * Sends SASL continuation data. + */ +static CURLcode imap_continue_authenticate(struct Curl_easy *data, + const char *mech, + const struct bufref *resp) +{ + struct imap_conn *imapc = &data->conn->proto.imapc; + + (void)mech; + + return Curl_pp_sendf(data, &imapc->pp, + "%s", (const char *) Curl_bufref_ptr(resp)); +} + +/*********************************************************************** + * + * imap_cancel_authenticate() + * + * Sends SASL cancellation. + */ +static CURLcode imap_cancel_authenticate(struct Curl_easy *data, + const char *mech) +{ + struct imap_conn *imapc = &data->conn->proto.imapc; + + (void)mech; + + return Curl_pp_sendf(data, &imapc->pp, "*"); +} + +/*********************************************************************** + * + * imap_perform_authentication() + * + * Initiates the authentication sequence, with the appropriate SASL + * authentication mechanism, falling back to clear text should a common + * mechanism not be available between the client and server. + */ +static CURLcode imap_perform_authentication(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct imap_conn *imapc = &conn->proto.imapc; + saslprogress progress; + + /* Check if already authenticated OR if there is enough data to authenticate + with and end the connect phase if we don't */ + if(imapc->preauth || + !Curl_sasl_can_authenticate(&imapc->sasl, data)) { + imap_state(data, IMAP_STOP); + return result; + } + + /* Calculate the SASL login details */ + result = Curl_sasl_start(&imapc->sasl, data, imapc->ir_supported, &progress); + + if(!result) { + if(progress == SASL_INPROGRESS) + imap_state(data, IMAP_AUTHENTICATE); + else if(!imapc->login_disabled && (imapc->preftype & IMAP_TYPE_CLEARTEXT)) + /* Perform clear text authentication */ + result = imap_perform_login(data, conn); + else { + /* Other mechanisms not supported */ + infof(data, "No known authentication mechanisms supported"); + result = CURLE_LOGIN_DENIED; + } + } + + return result; +} + +/*********************************************************************** + * + * imap_perform_list() + * + * Sends a LIST command or an alternative custom request. + */ +static CURLcode imap_perform_list(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct IMAP *imap = data->req.p.imap; + + if(imap->custom) + /* Send the custom request */ + result = imap_sendf(data, "%s%s", imap->custom, + imap->custom_params ? imap->custom_params : ""); + else { + /* Make sure the mailbox is in the correct atom format if necessary */ + char *mailbox = imap->mailbox ? imap_atom(imap->mailbox, true) + : strdup(""); + if(!mailbox) + return CURLE_OUT_OF_MEMORY; + + /* Send the LIST command */ + result = imap_sendf(data, "LIST \"%s\" *", mailbox); + + free(mailbox); + } + + if(!result) + imap_state(data, IMAP_LIST); + + return result; +} + +/*********************************************************************** + * + * imap_perform_select() + * + * Sends a SELECT command to ask the server to change the selected mailbox. + */ +static CURLcode imap_perform_select(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct IMAP *imap = data->req.p.imap; + struct imap_conn *imapc = &conn->proto.imapc; + char *mailbox; + + /* Invalidate old information as we are switching mailboxes */ + Curl_safefree(imapc->mailbox); + Curl_safefree(imapc->mailbox_uidvalidity); + + /* Check we have a mailbox */ + if(!imap->mailbox) { + failf(data, "Cannot SELECT without a mailbox."); + return CURLE_URL_MALFORMAT; + } + + /* Make sure the mailbox is in the correct atom format */ + mailbox = imap_atom(imap->mailbox, false); + if(!mailbox) + return CURLE_OUT_OF_MEMORY; + + /* Send the SELECT command */ + result = imap_sendf(data, "SELECT %s", mailbox); + + free(mailbox); + + if(!result) + imap_state(data, IMAP_SELECT); + + return result; +} + +/*********************************************************************** + * + * imap_perform_fetch() + * + * Sends a FETCH command to initiate the download of a message. + */ +static CURLcode imap_perform_fetch(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct IMAP *imap = data->req.p.imap; + /* Check we have a UID */ + if(imap->uid) { + + /* Send the FETCH command */ + if(imap->partial) + result = imap_sendf(data, "UID FETCH %s BODY[%s]<%s>", + imap->uid, imap->section ? imap->section : "", + imap->partial); + else + result = imap_sendf(data, "UID FETCH %s BODY[%s]", + imap->uid, imap->section ? imap->section : ""); + } + else if(imap->mindex) { + /* Send the FETCH command */ + if(imap->partial) + result = imap_sendf(data, "FETCH %s BODY[%s]<%s>", + imap->mindex, imap->section ? imap->section : "", + imap->partial); + else + result = imap_sendf(data, "FETCH %s BODY[%s]", + imap->mindex, imap->section ? imap->section : ""); + } + else { + failf(data, "Cannot FETCH without a UID."); + return CURLE_URL_MALFORMAT; + } + if(!result) + imap_state(data, IMAP_FETCH); + + return result; +} + +/*********************************************************************** + * + * imap_perform_append() + * + * Sends an APPEND command to initiate the upload of a message. + */ +static CURLcode imap_perform_append(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct IMAP *imap = data->req.p.imap; + char *mailbox; + + /* Check we have a mailbox */ + if(!imap->mailbox) { + failf(data, "Cannot APPEND without a mailbox."); + return CURLE_URL_MALFORMAT; + } + + /* Prepare the mime data if some. */ + if(data->set.mimepost.kind != MIMEKIND_NONE) { + /* Use the whole structure as data. */ + data->set.mimepost.flags &= ~MIME_BODY_ONLY; + + /* Add external headers and mime version. */ + curl_mime_headers(&data->set.mimepost, data->set.headers, 0); + result = Curl_mime_prepare_headers(data, &data->set.mimepost, NULL, + NULL, MIMESTRATEGY_MAIL); + + if(!result) + if(!Curl_checkheaders(data, STRCONST("Mime-Version"))) + result = Curl_mime_add_header(&data->set.mimepost.curlheaders, + "Mime-Version: 1.0"); + + /* Make sure we will read the entire mime structure. */ + if(!result) + result = Curl_mime_rewind(&data->set.mimepost); + + if(result) + return result; + + data->state.infilesize = Curl_mime_size(&data->set.mimepost); + + /* Read from mime structure. */ + data->state.fread_func = (curl_read_callback) Curl_mime_read; + data->state.in = (void *) &data->set.mimepost; + } + + /* Check we know the size of the upload */ + if(data->state.infilesize < 0) { + failf(data, "Cannot APPEND with unknown input file size"); + return CURLE_UPLOAD_FAILED; + } + + /* Make sure the mailbox is in the correct atom format */ + mailbox = imap_atom(imap->mailbox, false); + if(!mailbox) + return CURLE_OUT_OF_MEMORY; + + /* Send the APPEND command */ + result = imap_sendf(data, + "APPEND %s (\\Seen) {%" CURL_FORMAT_CURL_OFF_T "}", + mailbox, data->state.infilesize); + + free(mailbox); + + if(!result) + imap_state(data, IMAP_APPEND); + + return result; +} + +/*********************************************************************** + * + * imap_perform_search() + * + * Sends a SEARCH command. + */ +static CURLcode imap_perform_search(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct IMAP *imap = data->req.p.imap; + + /* Check we have a query string */ + if(!imap->query) { + failf(data, "Cannot SEARCH without a query string."); + return CURLE_URL_MALFORMAT; + } + + /* Send the SEARCH command */ + result = imap_sendf(data, "SEARCH %s", imap->query); + + if(!result) + imap_state(data, IMAP_SEARCH); + + return result; +} + +/*********************************************************************** + * + * imap_perform_logout() + * + * Performs the logout action prior to sclose() being called. + */ +static CURLcode imap_perform_logout(struct Curl_easy *data) +{ + /* Send the LOGOUT command */ + CURLcode result = imap_sendf(data, "LOGOUT"); + + if(!result) + imap_state(data, IMAP_LOGOUT); + + return result; +} + +/* For the initial server greeting */ +static CURLcode imap_state_servergreet_resp(struct Curl_easy *data, + int imapcode, + imapstate instate) +{ + struct connectdata *conn = data->conn; + (void)instate; /* no use for this yet */ + + if(imapcode == IMAP_RESP_PREAUTH) { + /* PREAUTH */ + struct imap_conn *imapc = &conn->proto.imapc; + imapc->preauth = TRUE; + infof(data, "PREAUTH connection, already authenticated"); + } + else if(imapcode != IMAP_RESP_OK) { + failf(data, "Got unexpected imap-server response"); + return CURLE_WEIRD_SERVER_REPLY; + } + + return imap_perform_capability(data, conn); +} + +/* For CAPABILITY responses */ +static CURLcode imap_state_capability_resp(struct Curl_easy *data, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct imap_conn *imapc = &conn->proto.imapc; + const char *line = data->state.buffer; + + (void)instate; /* no use for this yet */ + + /* Do we have a untagged response? */ + if(imapcode == '*') { + line += 2; + + /* Loop through the data line */ + for(;;) { + size_t wordlen; + while(*line && + (*line == ' ' || *line == '\t' || + *line == '\r' || *line == '\n')) { + + line++; + } + + if(!*line) + break; + + /* Extract the word */ + for(wordlen = 0; line[wordlen] && line[wordlen] != ' ' && + line[wordlen] != '\t' && line[wordlen] != '\r' && + line[wordlen] != '\n';) + wordlen++; + + /* Does the server support the STARTTLS capability? */ + if(wordlen == 8 && !memcmp(line, "STARTTLS", 8)) + imapc->tls_supported = TRUE; + + /* Has the server explicitly disabled clear text authentication? */ + else if(wordlen == 13 && !memcmp(line, "LOGINDISABLED", 13)) + imapc->login_disabled = TRUE; + + /* Does the server support the SASL-IR capability? */ + else if(wordlen == 7 && !memcmp(line, "SASL-IR", 7)) + imapc->ir_supported = TRUE; + + /* Do we have a SASL based authentication mechanism? */ + else if(wordlen > 5 && !memcmp(line, "AUTH=", 5)) { + size_t llen; + unsigned short mechbit; + + line += 5; + wordlen -= 5; + + /* Test the word for a matching authentication mechanism */ + mechbit = Curl_sasl_decode_mech(line, wordlen, &llen); + if(mechbit && llen == wordlen) + imapc->sasl.authmechs |= mechbit; + } + + line += wordlen; + } + } + else if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + /* PREAUTH is not compatible with STARTTLS. */ + if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) { + /* Switch to TLS connection now */ + result = imap_perform_starttls(data); + } + else if(data->set.use_ssl <= CURLUSESSL_TRY) + result = imap_perform_authentication(data, conn); + else { + failf(data, "STARTTLS not available."); + result = CURLE_USE_SSL_FAILED; + } + } + else + result = imap_perform_authentication(data, conn); + + return result; +} + +/* For STARTTLS responses */ +static CURLcode imap_state_starttls_resp(struct Curl_easy *data, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + (void)instate; /* no use for this yet */ + + /* Pipelining in response is forbidden. */ + if(data->conn->proto.imapc.pp.cache_size) + return CURLE_WEIRD_SERVER_REPLY; + + if(imapcode != IMAP_RESP_OK) { + if(data->set.use_ssl != CURLUSESSL_TRY) { + failf(data, "STARTTLS denied"); + result = CURLE_USE_SSL_FAILED; + } + else + result = imap_perform_authentication(data, conn); + } + else + result = imap_perform_upgrade_tls(data, conn); + + return result; +} + +/* For SASL authentication responses */ +static CURLcode imap_state_auth_resp(struct Curl_easy *data, + struct connectdata *conn, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct imap_conn *imapc = &conn->proto.imapc; + saslprogress progress; + + (void)instate; /* no use for this yet */ + + result = Curl_sasl_continue(&imapc->sasl, data, imapcode, &progress); + if(!result) + switch(progress) { + case SASL_DONE: + imap_state(data, IMAP_STOP); /* Authenticated */ + break; + case SASL_IDLE: /* No mechanism left after cancellation */ + if((!imapc->login_disabled) && (imapc->preftype & IMAP_TYPE_CLEARTEXT)) + /* Perform clear text authentication */ + result = imap_perform_login(data, conn); + else { + failf(data, "Authentication cancelled"); + result = CURLE_LOGIN_DENIED; + } + break; + default: + break; + } + + return result; +} + +/* For LOGIN responses */ +static CURLcode imap_state_login_resp(struct Curl_easy *data, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + (void)instate; /* no use for this yet */ + + if(imapcode != IMAP_RESP_OK) { + failf(data, "Access denied. %c", imapcode); + result = CURLE_LOGIN_DENIED; + } + else + /* End of connect phase */ + imap_state(data, IMAP_STOP); + + return result; +} + +/* For LIST and SEARCH responses */ +static CURLcode imap_state_listsearch_resp(struct Curl_easy *data, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + char *line = data->state.buffer; + size_t len = strlen(line); + + (void)instate; /* No use for this yet */ + + if(imapcode == '*') { + /* Temporarily add the LF character back and send as body to the client */ + line[len] = '\n'; + result = Curl_client_write(data, CLIENTWRITE_BODY, line, len + 1); + line[len] = '\0'; + } + else if(imapcode != IMAP_RESP_OK) + result = CURLE_QUOTE_ERROR; + else + /* End of DO phase */ + imap_state(data, IMAP_STOP); + + return result; +} + +/* For SELECT responses */ +static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct IMAP *imap = data->req.p.imap; + struct imap_conn *imapc = &conn->proto.imapc; + const char *line = data->state.buffer; + + (void)instate; /* no use for this yet */ + + if(imapcode == '*') { + /* See if this is an UIDVALIDITY response */ + if(checkprefix("OK [UIDVALIDITY ", line + 2)) { + size_t len = 0; + const char *p = &line[2] + strlen("OK [UIDVALIDITY "); + while((len < 20) && p[len] && ISDIGIT(p[len])) + len++; + if(len && (p[len] == ']')) { + struct dynbuf uid; + Curl_dyn_init(&uid, 20); + if(Curl_dyn_addn(&uid, p, len)) + return CURLE_OUT_OF_MEMORY; + Curl_safefree(imapc->mailbox_uidvalidity); + imapc->mailbox_uidvalidity = Curl_dyn_ptr(&uid); + } + } + } + else if(imapcode == IMAP_RESP_OK) { + /* Check if the UIDVALIDITY has been specified and matches */ + if(imap->uidvalidity && imapc->mailbox_uidvalidity && + !strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity)) { + failf(data, "Mailbox UIDVALIDITY has changed"); + result = CURLE_REMOTE_FILE_NOT_FOUND; + } + else { + /* Note the currently opened mailbox on this connection */ + DEBUGASSERT(!imapc->mailbox); + imapc->mailbox = strdup(imap->mailbox); + if(!imapc->mailbox) + return CURLE_OUT_OF_MEMORY; + + if(imap->custom) + result = imap_perform_list(data); + else if(imap->query) + result = imap_perform_search(data); + else + result = imap_perform_fetch(data); + } + } + else { + failf(data, "Select failed"); + result = CURLE_LOGIN_DENIED; + } + + return result; +} + +/* For the (first line of the) FETCH responses */ +static CURLcode imap_state_fetch_resp(struct Curl_easy *data, + struct connectdata *conn, int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct imap_conn *imapc = &conn->proto.imapc; + struct pingpong *pp = &imapc->pp; + const char *ptr = data->state.buffer; + bool parsed = FALSE; + curl_off_t size = 0; + + (void)instate; /* no use for this yet */ + + if(imapcode != '*') { + Curl_pgrsSetDownloadSize(data, -1); + imap_state(data, IMAP_STOP); + return CURLE_REMOTE_FILE_NOT_FOUND; + } + + /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse + the continuation data contained within the curly brackets */ + while(*ptr && (*ptr != '{')) + ptr++; + + if(*ptr == '{') { + char *endptr; + if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size)) { + if(endptr - ptr > 1 && endptr[0] == '}' && + endptr[1] == '\r' && endptr[2] == '\0') + parsed = TRUE; + } + } + + if(parsed) { + infof(data, "Found %" CURL_FORMAT_CURL_OFF_T " bytes to download", + size); + Curl_pgrsSetDownloadSize(data, size); + + if(pp->cache) { + /* At this point there is a bunch of data in the header "cache" that is + actually body content, send it as body and then skip it. Do note + that there may even be additional "headers" after the body. */ + size_t chunk = pp->cache_size; + + if(chunk > (size_t)size) + /* The conversion from curl_off_t to size_t is always fine here */ + chunk = (size_t)size; + + if(!chunk) { + /* no size, we're done with the data */ + imap_state(data, IMAP_STOP); + return CURLE_OK; + } + result = Curl_client_write(data, CLIENTWRITE_BODY, pp->cache, chunk); + if(result) + return result; + + infof(data, "Written %zu bytes, %" CURL_FORMAT_CURL_OFF_TU + " bytes are left for transfer", chunk, size - chunk); + + /* Have we used the entire cache or just part of it?*/ + if(pp->cache_size > chunk) { + /* Only part of it so shrink the cache to fit the trailing data */ + memmove(pp->cache, pp->cache + chunk, pp->cache_size - chunk); + pp->cache_size -= chunk; + } + else { + /* Free the cache */ + Curl_safefree(pp->cache); + + /* Reset the cache size */ + pp->cache_size = 0; + } + } + + if(data->req.bytecount == size) + /* The entire data is already transferred! */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + else { + /* IMAP download */ + data->req.maxdownload = size; + /* force a recv/send check of this connection, as the data might've been + read off the socket already */ + data->conn->cselect_bits = CURL_CSELECT_IN; + Curl_setup_transfer(data, FIRSTSOCKET, size, FALSE, -1); + } + } + else { + /* We don't know how to parse this line */ + failf(data, "Failed to parse FETCH response."); + result = CURLE_WEIRD_SERVER_REPLY; + } + + /* End of DO phase */ + imap_state(data, IMAP_STOP); + + return result; +} + +/* For final FETCH responses performed after the download */ +static CURLcode imap_state_fetch_final_resp(struct Curl_easy *data, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + + (void)instate; /* No use for this yet */ + + if(imapcode != IMAP_RESP_OK) + result = CURLE_WEIRD_SERVER_REPLY; + else + /* End of DONE phase */ + imap_state(data, IMAP_STOP); + + return result; +} + +/* For APPEND responses */ +static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + (void)instate; /* No use for this yet */ + + if(imapcode != '+') { + result = CURLE_UPLOAD_FAILED; + } + else { + /* Set the progress upload size */ + Curl_pgrsSetUploadSize(data, data->state.infilesize); + + /* IMAP upload */ + Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + + /* End of DO phase */ + imap_state(data, IMAP_STOP); + } + + return result; +} + +/* For final APPEND responses performed after the upload */ +static CURLcode imap_state_append_final_resp(struct Curl_easy *data, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + + (void)instate; /* No use for this yet */ + + if(imapcode != IMAP_RESP_OK) + result = CURLE_UPLOAD_FAILED; + else + /* End of DONE phase */ + imap_state(data, IMAP_STOP); + + return result; +} + +static CURLcode imap_statemachine(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int imapcode; + struct imap_conn *imapc = &conn->proto.imapc; + struct pingpong *pp = &imapc->pp; + size_t nread = 0; + (void)data; + + /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */ + if(imapc->state == IMAP_UPGRADETLS) + return imap_perform_upgrade_tls(data, conn); + + /* Flush any data that needs to be sent */ + if(pp->sendleft) + return Curl_pp_flushsend(data, pp); + + do { + /* Read the response from the server */ + result = Curl_pp_readresp(data, sock, pp, &imapcode, &nread); + if(result) + return result; + + /* Was there an error parsing the response line? */ + if(imapcode == -1) + return CURLE_WEIRD_SERVER_REPLY; + + if(!imapcode) + break; + + /* We have now received a full IMAP server response */ + switch(imapc->state) { + case IMAP_SERVERGREET: + result = imap_state_servergreet_resp(data, imapcode, imapc->state); + break; + + case IMAP_CAPABILITY: + result = imap_state_capability_resp(data, imapcode, imapc->state); + break; + + case IMAP_STARTTLS: + result = imap_state_starttls_resp(data, imapcode, imapc->state); + break; + + case IMAP_AUTHENTICATE: + result = imap_state_auth_resp(data, conn, imapcode, imapc->state); + break; + + case IMAP_LOGIN: + result = imap_state_login_resp(data, imapcode, imapc->state); + break; + + case IMAP_LIST: + case IMAP_SEARCH: + result = imap_state_listsearch_resp(data, imapcode, imapc->state); + break; + + case IMAP_SELECT: + result = imap_state_select_resp(data, imapcode, imapc->state); + break; + + case IMAP_FETCH: + result = imap_state_fetch_resp(data, conn, imapcode, imapc->state); + break; + + case IMAP_FETCH_FINAL: + result = imap_state_fetch_final_resp(data, imapcode, imapc->state); + break; + + case IMAP_APPEND: + result = imap_state_append_resp(data, imapcode, imapc->state); + break; + + case IMAP_APPEND_FINAL: + result = imap_state_append_final_resp(data, imapcode, imapc->state); + break; + + case IMAP_LOGOUT: + /* fallthrough, just stop! */ + default: + /* internal error */ + imap_state(data, IMAP_STOP); + break; + } + } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp)); + + return result; +} + +/* Called repeatedly until done from multi.c */ +static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct imap_conn *imapc = &conn->proto.imapc; + + if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) { + bool ssldone = FALSE; + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + imapc->ssldone = ssldone; + if(result || !ssldone) + return result; + } + + result = Curl_pp_statemach(data, &imapc->pp, FALSE, FALSE); + *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE; + + return result; +} + +static CURLcode imap_block_statemach(struct Curl_easy *data, + struct connectdata *conn, + bool disconnecting) +{ + CURLcode result = CURLE_OK; + struct imap_conn *imapc = &conn->proto.imapc; + + while(imapc->state != IMAP_STOP && !result) + result = Curl_pp_statemach(data, &imapc->pp, TRUE, disconnecting); + + return result; +} + +/* Allocate and initialize the struct IMAP for the current Curl_easy if + required */ +static CURLcode imap_init(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct IMAP *imap; + + imap = data->req.p.imap = calloc(1, sizeof(struct IMAP)); + if(!imap) + result = CURLE_OUT_OF_MEMORY; + + return result; +} + +/* For the IMAP "protocol connect" and "doing" phases only */ +static int imap_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) +{ + return Curl_pp_getsock(data, &conn->proto.imapc.pp, socks); +} + +/*********************************************************************** + * + * imap_connect() + * + * This function should do everything that is to be considered a part of the + * connection phase. + * + * The variable 'done' points to will be TRUE if the protocol-layer connect + * phase is done when this function returns, or FALSE if not. + */ +static CURLcode imap_connect(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct imap_conn *imapc = &conn->proto.imapc; + struct pingpong *pp = &imapc->pp; + + *done = FALSE; /* default to not done yet */ + + /* We always support persistent connections in IMAP */ + connkeep(conn, "IMAP default"); + + PINGPONG_SETUP(pp, imap_statemachine, imap_endofresp); + + /* Set the default preferred authentication type and mechanism */ + imapc->preftype = IMAP_TYPE_ANY; + Curl_sasl_init(&imapc->sasl, data, &saslimap); + + Curl_dyn_init(&imapc->dyn, DYN_IMAP_CMD); + /* Initialise the pingpong layer */ + Curl_pp_setup(pp); + Curl_pp_init(data, pp); + + /* Parse the URL options */ + result = imap_parse_url_options(conn); + if(result) + return result; + + /* Start off waiting for the server greeting response */ + imap_state(data, IMAP_SERVERGREET); + + /* Start off with an response id of '*' */ + strcpy(imapc->resptag, "*"); + + result = imap_multi_statemach(data, done); + + return result; +} + +/*********************************************************************** + * + * imap_done() + * + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. + */ +static CURLcode imap_done(struct Curl_easy *data, CURLcode status, + bool premature) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct IMAP *imap = data->req.p.imap; + + (void)premature; + + if(!imap) + return CURLE_OK; + + if(status) { + connclose(conn, "IMAP done with bad status"); /* marked for closure */ + result = status; /* use the already set error code */ + } + else if(!data->set.connect_only && !imap->custom && + (imap->uid || imap->mindex || data->state.upload || + data->set.mimepost.kind != MIMEKIND_NONE)) { + /* Handle responses after FETCH or APPEND transfer has finished */ + + if(!data->state.upload && data->set.mimepost.kind == MIMEKIND_NONE) + imap_state(data, IMAP_FETCH_FINAL); + else { + /* End the APPEND command first by sending an empty line */ + result = Curl_pp_sendf(data, &conn->proto.imapc.pp, "%s", ""); + if(!result) + imap_state(data, IMAP_APPEND_FINAL); + } + + /* Run the state-machine */ + if(!result) + result = imap_block_statemach(data, conn, FALSE); + } + + /* Cleanup our per-request based variables */ + Curl_safefree(imap->mailbox); + Curl_safefree(imap->uidvalidity); + Curl_safefree(imap->uid); + Curl_safefree(imap->mindex); + Curl_safefree(imap->section); + Curl_safefree(imap->partial); + Curl_safefree(imap->query); + Curl_safefree(imap->custom); + Curl_safefree(imap->custom_params); + + /* Clear the transfer mode for the next request */ + imap->transfer = PPTRANSFER_BODY; + + return result; +} + +/*********************************************************************** + * + * imap_perform() + * + * This is the actual DO function for IMAP. Fetch or append a message, or do + * other things according to the options previously setup. + */ +static CURLcode imap_perform(struct Curl_easy *data, bool *connected, + bool *dophase_done) +{ + /* This is IMAP and no proxy */ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct IMAP *imap = data->req.p.imap; + struct imap_conn *imapc = &conn->proto.imapc; + bool selected = FALSE; + + DEBUGF(infof(data, "DO phase starts")); + + if(data->req.no_body) { + /* Requested no body means no transfer */ + imap->transfer = PPTRANSFER_INFO; + } + + *dophase_done = FALSE; /* not done yet */ + + /* Determine if the requested mailbox (with the same UIDVALIDITY if set) + has already been selected on this connection */ + if(imap->mailbox && imapc->mailbox && + strcasecompare(imap->mailbox, imapc->mailbox) && + (!imap->uidvalidity || !imapc->mailbox_uidvalidity || + strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity))) + selected = TRUE; + + /* Start the first command in the DO phase */ + if(data->state.upload || data->set.mimepost.kind != MIMEKIND_NONE) + /* APPEND can be executed directly */ + result = imap_perform_append(data); + else if(imap->custom && (selected || !imap->mailbox)) + /* Custom command using the same mailbox or no mailbox */ + result = imap_perform_list(data); + else if(!imap->custom && selected && (imap->uid || imap->mindex)) + /* FETCH from the same mailbox */ + result = imap_perform_fetch(data); + else if(!imap->custom && selected && imap->query) + /* SEARCH the current mailbox */ + result = imap_perform_search(data); + else if(imap->mailbox && !selected && + (imap->custom || imap->uid || imap->mindex || imap->query)) + /* SELECT the mailbox */ + result = imap_perform_select(data); + else + /* LIST */ + result = imap_perform_list(data); + + if(result) + return result; + + /* Run the state-machine */ + result = imap_multi_statemach(data, dophase_done); + + *connected = Curl_conn_is_connected(conn, FIRSTSOCKET); + + if(*dophase_done) + DEBUGF(infof(data, "DO phase is complete")); + + return result; +} + +/*********************************************************************** + * + * imap_do() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (imap_perform). + * + * The input argument is already checked for validity. + */ +static CURLcode imap_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + *done = FALSE; /* default to false */ + + /* Parse the URL path */ + result = imap_parse_url_path(data); + if(result) + return result; + + /* Parse the custom request */ + result = imap_parse_custom_request(data); + if(result) + return result; + + result = imap_regular_transfer(data, done); + + return result; +} + +/*********************************************************************** + * + * imap_disconnect() + * + * Disconnect from an IMAP server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +static CURLcode imap_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection) +{ + struct imap_conn *imapc = &conn->proto.imapc; + (void)data; + + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. */ + + /* The IMAP session may or may not have been allocated/setup at this + point! */ + if(!dead_connection && conn->bits.protoconnstart) { + if(!imap_perform_logout(data)) + (void)imap_block_statemach(data, conn, TRUE); /* ignore errors */ + } + + /* Disconnect from the server */ + Curl_pp_disconnect(&imapc->pp); + Curl_dyn_free(&imapc->dyn); + + /* Cleanup the SASL module */ + Curl_sasl_cleanup(conn, imapc->sasl.authused); + + /* Cleanup our connection based variables */ + Curl_safefree(imapc->mailbox); + Curl_safefree(imapc->mailbox_uidvalidity); + + return CURLE_OK; +} + +/* Call this when the DO phase has completed */ +static CURLcode imap_dophase_done(struct Curl_easy *data, bool connected) +{ + struct IMAP *imap = data->req.p.imap; + + (void)connected; + + if(imap->transfer != PPTRANSFER_BODY) + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + + return CURLE_OK; +} + +/* Called from multi.c while DOing */ +static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done) +{ + CURLcode result = imap_multi_statemach(data, dophase_done); + + if(result) + DEBUGF(infof(data, "DO phase failed")); + else if(*dophase_done) { + result = imap_dophase_done(data, FALSE /* not connected */); + + DEBUGF(infof(data, "DO phase is complete")); + } + + return result; +} + +/*********************************************************************** + * + * imap_regular_transfer() + * + * The input argument is already checked for validity. + * + * Performs all commands done before a regular transfer between a local and a + * remote host. + */ +static CURLcode imap_regular_transfer(struct Curl_easy *data, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + bool connected = FALSE; + + /* Make sure size is unknown at this point */ + data->req.size = -1; + + /* Set the progress data */ + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, -1); + Curl_pgrsSetDownloadSize(data, -1); + + /* Carry out the perform */ + result = imap_perform(data, &connected, dophase_done); + + /* Perform post DO phase operations if necessary */ + if(!result && *dophase_done) + result = imap_dophase_done(data, connected); + + return result; +} + +static CURLcode imap_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + /* Initialise the IMAP layer */ + CURLcode result = imap_init(data); + if(result) + return result; + + /* Clear the TLS upgraded flag */ + conn->bits.tls_upgraded = FALSE; + + return CURLE_OK; +} + +/*********************************************************************** + * + * imap_sendf() + * + * Sends the formatted string as an IMAP command to the server. + * + * Designed to never block. + */ +static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) +{ + CURLcode result = CURLE_OK; + struct imap_conn *imapc = &data->conn->proto.imapc; + + DEBUGASSERT(fmt); + + /* Calculate the tag based on the connection ID and command ID */ + msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d", + 'A' + curlx_sltosi((long)(data->conn->connection_id % 26)), + ++imapc->cmdid); + + /* start with a blank buffer */ + Curl_dyn_reset(&imapc->dyn); + + /* append tag + space + fmt */ + result = Curl_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt); + if(!result) { + va_list ap; + va_start(ap, fmt); + result = Curl_pp_vsendf(data, &imapc->pp, Curl_dyn_ptr(&imapc->dyn), ap); + va_end(ap); + } + return result; +} + +/*********************************************************************** + * + * imap_atom() + * + * Checks the input string for characters that need escaping and returns an + * atom ready for sending to the server. + * + * The returned string needs to be freed. + * + */ +static char *imap_atom(const char *str, bool escape_only) +{ + struct dynbuf line; + size_t nclean; + size_t len; + + if(!str) + return NULL; + + len = strlen(str); + nclean = strcspn(str, "() {%*]\\\""); + if(len == nclean) + /* nothing to escape, return a strdup */ + return strdup(str); + + Curl_dyn_init(&line, 2000); + + if(!escape_only && Curl_dyn_addn(&line, "\"", 1)) + return NULL; + + while(*str) { + if((*str == '\\' || *str == '"') && + Curl_dyn_addn(&line, "\\", 1)) + return NULL; + if(Curl_dyn_addn(&line, str, 1)) + return NULL; + str++; + } + + if(!escape_only && Curl_dyn_addn(&line, "\"", 1)) + return NULL; + + return Curl_dyn_ptr(&line); +} + +/*********************************************************************** + * + * imap_is_bchar() + * + * Portable test of whether the specified char is a "bchar" as defined in the + * grammar of RFC-5092. + */ +static bool imap_is_bchar(char ch) +{ + /* Performing the alnum check with this macro is faster because of ASCII + arithmetic */ + if(ISALNUM(ch)) + return true; + + switch(ch) { + /* bchar */ + case ':': case '@': case '/': + /* bchar -> achar */ + case '&': case '=': + /* bchar -> achar -> uchar -> unreserved (without alphanumeric) */ + case '-': case '.': case '_': case '~': + /* bchar -> achar -> uchar -> sub-delims-sh */ + case '!': case '$': case '\'': case '(': case ')': case '*': + case '+': case ',': + /* bchar -> achar -> uchar -> pct-encoded */ + case '%': /* HEXDIG chars are already included above */ + return true; + + default: + return false; + } +} + +/*********************************************************************** + * + * imap_parse_url_options() + * + * Parse the URL login options. + */ +static CURLcode imap_parse_url_options(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct imap_conn *imapc = &conn->proto.imapc; + const char *ptr = conn->options; + bool prefer_login = false; + + while(!result && ptr && *ptr) { + const char *key = ptr; + const char *value; + + while(*ptr && *ptr != '=') + ptr++; + + value = ptr + 1; + + while(*ptr && *ptr != ';') + ptr++; + + if(strncasecompare(key, "AUTH=+LOGIN", 11)) { + /* User prefers plaintext LOGIN over any SASL, including SASL LOGIN */ + prefer_login = true; + imapc->sasl.prefmech = SASL_AUTH_NONE; + } + else if(strncasecompare(key, "AUTH=", 5)) { + prefer_login = false; + result = Curl_sasl_parse_url_auth_option(&imapc->sasl, + value, ptr - value); + } + else { + prefer_login = false; + result = CURLE_URL_MALFORMAT; + } + + if(*ptr == ';') + ptr++; + } + + if(prefer_login) + imapc->preftype = IMAP_TYPE_CLEARTEXT; + else { + switch(imapc->sasl.prefmech) { + case SASL_AUTH_NONE: + imapc->preftype = IMAP_TYPE_NONE; + break; + case SASL_AUTH_DEFAULT: + imapc->preftype = IMAP_TYPE_ANY; + break; + default: + imapc->preftype = IMAP_TYPE_SASL; + break; + } + } + + return result; +} + +/*********************************************************************** + * + * imap_parse_url_path() + * + * Parse the URL path into separate path components. + * + */ +static CURLcode imap_parse_url_path(struct Curl_easy *data) +{ + /* The imap struct is already initialised in imap_connect() */ + CURLcode result = CURLE_OK; + struct IMAP *imap = data->req.p.imap; + const char *begin = &data->state.up.path[1]; /* skip leading slash */ + const char *ptr = begin; + + /* See how much of the URL is a valid path and decode it */ + while(imap_is_bchar(*ptr)) + ptr++; + + if(ptr != begin) { + /* Remove the trailing slash if present */ + const char *end = ptr; + if(end > begin && end[-1] == '/') + end--; + + result = Curl_urldecode(begin, end - begin, &imap->mailbox, NULL, + REJECT_CTRL); + if(result) + return result; + } + else + imap->mailbox = NULL; + + /* There can be any number of parameters in the form ";NAME=VALUE" */ + while(*ptr == ';') { + char *name; + char *value; + size_t valuelen; + + /* Find the length of the name parameter */ + begin = ++ptr; + while(*ptr && *ptr != '=') + ptr++; + + if(!*ptr) + return CURLE_URL_MALFORMAT; + + /* Decode the name parameter */ + result = Curl_urldecode(begin, ptr - begin, &name, NULL, + REJECT_CTRL); + if(result) + return result; + + /* Find the length of the value parameter */ + begin = ++ptr; + while(imap_is_bchar(*ptr)) + ptr++; + + /* Decode the value parameter */ + result = Curl_urldecode(begin, ptr - begin, &value, &valuelen, + REJECT_CTRL); + if(result) { + free(name); + return result; + } + + DEBUGF(infof(data, "IMAP URL parameter '%s' = '%s'", name, value)); + + /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION and + PARTIAL) stripping of the trailing slash character if it is present. + + Note: Unknown parameters trigger a URL_MALFORMAT error. */ + if(strcasecompare(name, "UIDVALIDITY") && !imap->uidvalidity) { + if(valuelen > 0 && value[valuelen - 1] == '/') + value[valuelen - 1] = '\0'; + + imap->uidvalidity = value; + value = NULL; + } + else if(strcasecompare(name, "UID") && !imap->uid) { + if(valuelen > 0 && value[valuelen - 1] == '/') + value[valuelen - 1] = '\0'; + + imap->uid = value; + value = NULL; + } + else if(strcasecompare(name, "MAILINDEX") && !imap->mindex) { + if(valuelen > 0 && value[valuelen - 1] == '/') + value[valuelen - 1] = '\0'; + + imap->mindex = value; + value = NULL; + } + else if(strcasecompare(name, "SECTION") && !imap->section) { + if(valuelen > 0 && value[valuelen - 1] == '/') + value[valuelen - 1] = '\0'; + + imap->section = value; + value = NULL; + } + else if(strcasecompare(name, "PARTIAL") && !imap->partial) { + if(valuelen > 0 && value[valuelen - 1] == '/') + value[valuelen - 1] = '\0'; + + imap->partial = value; + value = NULL; + } + else { + free(name); + free(value); + + return CURLE_URL_MALFORMAT; + } + + free(name); + free(value); + } + + /* Does the URL contain a query parameter? Only valid when we have a mailbox + and no UID as per RFC-5092 */ + if(imap->mailbox && !imap->uid && !imap->mindex) { + /* Get the query parameter, URL decoded */ + (void)curl_url_get(data->state.uh, CURLUPART_QUERY, &imap->query, + CURLU_URLDECODE); + } + + /* Any extra stuff at the end of the URL is an error */ + if(*ptr) + return CURLE_URL_MALFORMAT; + + return CURLE_OK; +} + +/*********************************************************************** + * + * imap_parse_custom_request() + * + * Parse the custom request. + */ +static CURLcode imap_parse_custom_request(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct IMAP *imap = data->req.p.imap; + const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + + if(custom) { + /* URL decode the custom request */ + result = Curl_urldecode(custom, 0, &imap->custom, NULL, REJECT_CTRL); + + /* Extract the parameters if specified */ + if(!result) { + const char *params = imap->custom; + + while(*params && *params != ' ') + params++; + + if(*params) { + imap->custom_params = strdup(params); + imap->custom[params - imap->custom] = '\0'; + + if(!imap->custom_params) + result = CURLE_OUT_OF_MEMORY; + } + } + } + + return result; +} + +#endif /* CURL_DISABLE_IMAP */ diff --git a/Utilities/cmcurl/lib/imap.h b/Utilities/cmcurl/lib/imap.h new file mode 100644 index 0000000..784ee97 --- /dev/null +++ b/Utilities/cmcurl/lib/imap.h @@ -0,0 +1,101 @@ +#ifndef HEADER_CURL_IMAP_H +#define HEADER_CURL_IMAP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "pingpong.h" +#include "curl_sasl.h" + +/**************************************************************************** + * IMAP unique setup + ***************************************************************************/ +typedef enum { + IMAP_STOP, /* do nothing state, stops the state machine */ + IMAP_SERVERGREET, /* waiting for the initial greeting immediately after + a connect */ + IMAP_CAPABILITY, + IMAP_STARTTLS, + IMAP_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS + (multi mode only) */ + IMAP_AUTHENTICATE, + IMAP_LOGIN, + IMAP_LIST, + IMAP_SELECT, + IMAP_FETCH, + IMAP_FETCH_FINAL, + IMAP_APPEND, + IMAP_APPEND_FINAL, + IMAP_SEARCH, + IMAP_LOGOUT, + IMAP_LAST /* never used */ +} imapstate; + +/* This IMAP struct is used in the Curl_easy. All IMAP data that is + connection-oriented must be in imap_conn to properly deal with the fact that + perhaps the Curl_easy is changed between the times the connection is + used. */ +struct IMAP { + curl_pp_transfer transfer; + char *mailbox; /* Mailbox to select */ + char *uidvalidity; /* UIDVALIDITY to check in select */ + char *uid; /* Message UID to fetch */ + char *mindex; /* Index in mail box of mail to fetch */ + char *section; /* Message SECTION to fetch */ + char *partial; /* Message PARTIAL to fetch */ + char *query; /* Query to search for */ + char *custom; /* Custom request */ + char *custom_params; /* Parameters for the custom request */ +}; + +/* imap_conn is used for struct connection-oriented data in the connectdata + struct */ +struct imap_conn { + struct pingpong pp; + struct SASL sasl; /* SASL-related parameters */ + struct dynbuf dyn; /* for the IMAP commands */ + char *mailbox; /* The last selected mailbox */ + char *mailbox_uidvalidity; /* UIDVALIDITY parsed from select response */ + imapstate state; /* Always use imap.c:state() to change state! */ + char resptag[5]; /* Response tag to wait for */ + unsigned char preftype; /* Preferred authentication type */ + unsigned char cmdid; /* Last used command ID */ + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(preauth); /* Is this connection PREAUTH? */ + BIT(tls_supported); /* StartTLS capability supported by server */ + BIT(login_disabled); /* LOGIN command disabled by server */ + BIT(ir_supported); /* Initial response supported by server */ +}; + +extern const struct Curl_handler Curl_handler_imap; +extern const struct Curl_handler Curl_handler_imaps; + +/* Authentication type flags */ +#define IMAP_TYPE_CLEARTEXT (1 << 0) +#define IMAP_TYPE_SASL (1 << 1) + +/* Authentication type values */ +#define IMAP_TYPE_NONE 0 +#define IMAP_TYPE_ANY (IMAP_TYPE_CLEARTEXT|IMAP_TYPE_SASL) + +#endif /* HEADER_CURL_IMAP_H */ diff --git a/Utilities/cmcurl/lib/inet_ntop.c b/Utilities/cmcurl/lib/inet_ntop.c new file mode 100644 index 0000000..c9cee0c --- /dev/null +++ b/Utilities/cmcurl/lib/inet_ntop.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 1996-2022 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * SPDX-License-Identifier: ISC + */ +/* + * Original code by Paul Vixie. "curlified" by Gisle Vanem. + */ + +#include "curl_setup.h" + +#ifndef HAVE_INET_NTOP + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#include "inet_ntop.h" +#include "curl_printf.h" + +#define IN6ADDRSZ 16 +#define INADDRSZ 4 +#define INT16SZ 2 + +/* + * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * sure we have _some_ value for AF_INET6 without polluting our fake value + * everywhere. + */ +#if !defined(ENABLE_IPV6) && !defined(AF_INET6) +#define AF_INET6 (AF_INET + 1) +#endif + +/* + * Format an IPv4 address, more or less like inet_ntop(). + * + * Returns `dst' (as a const) + * Note: + * - uses no statics + * - takes a unsigned char* not an in_addr as input + */ +static char *inet_ntop4 (const unsigned char *src, char *dst, size_t size) +{ + char tmp[sizeof("255.255.255.255")]; + size_t len; + + DEBUGASSERT(size >= 16); + + tmp[0] = '\0'; + (void)msnprintf(tmp, sizeof(tmp), "%d.%d.%d.%d", + ((int)((unsigned char)src[0])) & 0xff, + ((int)((unsigned char)src[1])) & 0xff, + ((int)((unsigned char)src[2])) & 0xff, + ((int)((unsigned char)src[3])) & 0xff); + + len = strlen(tmp); + if(len == 0 || len >= size) { + errno = ENOSPC; + return (NULL); + } + strcpy(dst, tmp); + return dst; +} + +/* + * Convert IPv6 binary address into presentation (printable) format. + */ +static char *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 + * to contain a value of the specified size. On some systems, like + * Crays, there is no such thing as an integer variable with 16 bits. + * Keep this in mind if you think this function should have been coded + * to use pointer overlays. All the world's not a VAX. + */ + char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + char *tp; + struct { + int base; + int len; + } best, cur; + unsigned int words[IN6ADDRSZ / INT16SZ]; + int i; + + /* Preprocess: + * Copy the input (bytewise) array into a wordwise array. + * Find the longest run of 0x00's in src[] for :: shorthanding. + */ + memset(words, '\0', sizeof(words)); + for(i = 0; i < IN6ADDRSZ; i++) + words[i/2] |= ((unsigned int)src[i] << ((1 - (i % 2)) << 3)); + + best.base = -1; + cur.base = -1; + best.len = 0; + cur.len = 0; + + for(i = 0; i < (IN6ADDRSZ / INT16SZ); i++) { + if(words[i] == 0) { + if(cur.base == -1) { + cur.base = i; cur.len = 1; + } + else + cur.len++; + } + else if(cur.base != -1) { + if(best.base == -1 || cur.len > best.len) + best = cur; + cur.base = -1; + } + } + if((cur.base != -1) && (best.base == -1 || cur.len > best.len)) + best = cur; + if(best.base != -1 && best.len < 2) + best.base = -1; + /* Format the result. */ + tp = tmp; + for(i = 0; i < (IN6ADDRSZ / INT16SZ); i++) { + /* Are we inside the best run of 0x00's? */ + if(best.base != -1 && i >= best.base && i < (best.base + best.len)) { + if(i == best.base) + *tp++ = ':'; + continue; + } + + /* Are we following an initial run of 0x00s or any real hex? + */ + if(i) + *tp++ = ':'; + + /* Is this address an encapsulated IPv4? + */ + 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))) { + errno = ENOSPC; + return (NULL); + } + tp += strlen(tp); + break; + } + tp += msnprintf(tp, 5, "%x", words[i]); + } + + /* Was it a trailing run of 0x00's? + */ + if(best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ)) + *tp++ = ':'; + *tp++ = '\0'; + + /* Check for overflow, copy, and we're done. + */ + if((size_t)(tp - tmp) > size) { + errno = ENOSPC; + return (NULL); + } + strcpy(dst, tmp); + return dst; +} + +/* + * 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. So when this function returns + * NULL, check errno not SOCKERRNO. + */ +char *Curl_inet_ntop(int af, const void *src, char *buf, size_t size) +{ + switch(af) { + case AF_INET: + return inet_ntop4((const unsigned char *)src, buf, size); + case AF_INET6: + return inet_ntop6((const unsigned char *)src, buf, size); + default: + errno = EAFNOSUPPORT; + return NULL; + } +} +#endif /* HAVE_INET_NTOP */ diff --git a/Utilities/cmcurl/lib/inet_ntop.h b/Utilities/cmcurl/lib/inet_ntop.h new file mode 100644 index 0000000..7c3ead4 --- /dev/null +++ b/Utilities/cmcurl/lib/inet_ntop.h @@ -0,0 +1,39 @@ +#ifndef HEADER_CURL_INET_NTOP_H +#define HEADER_CURL_INET_NTOP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +char *Curl_inet_ntop(int af, const void *addr, char *buf, size_t size); + +#ifdef HAVE_INET_NTOP +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#define Curl_inet_ntop(af,addr,buf,size) \ + inet_ntop(af, addr, buf, (curl_socklen_t)size) +#endif + +#endif /* HEADER_CURL_INET_NTOP_H */ diff --git a/Utilities/cmcurl/lib/inet_pton.c b/Utilities/cmcurl/lib/inet_pton.c new file mode 100644 index 0000000..7d3c698 --- /dev/null +++ b/Utilities/cmcurl/lib/inet_pton.c @@ -0,0 +1,242 @@ +/* This is from the BIND 4.9.4 release, modified to compile by itself */ + +/* Copyright (c) Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * + * SPDX-License-Identifier: ISC + */ + +#include "curl_setup.h" + +#ifndef HAVE_INET_PTON + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#include "inet_pton.h" + +#define IN6ADDRSZ 16 +#define INADDRSZ 4 +#define INT16SZ 2 + +/* + * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * sure we have _some_ value for AF_INET6 without polluting our fake value + * everywhere. + */ +#if !defined(ENABLE_IPV6) && !defined(AF_INET6) +#define AF_INET6 (AF_INET + 1) +#endif + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +static int inet_pton4(const char *src, unsigned char *dst); +static int inet_pton6(const char *src, unsigned char *dst); + +/* 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 wasn't 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. So when this function returns + * -1, check errno not SOCKERRNO. + * author: + * Paul Vixie, 1996. + */ +int +Curl_inet_pton(int af, const char *src, void *dst) +{ + switch(af) { + case AF_INET: + return (inet_pton4(src, (unsigned char *)dst)); + case AF_INET6: + return (inet_pton6(src, (unsigned char *)dst)); + default: + errno = EAFNOSUPPORT; + return (-1); + } + /* NOTREACHED */ +} + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton4(const char *src, unsigned char *dst) +{ + static const char digits[] = "0123456789"; + int saw_digit, octets, ch; + unsigned char tmp[INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + tp = tmp; + *tp = 0; + while((ch = *src++) != '\0') { + const char *pch; + + pch = strchr(digits, ch); + if(pch) { + unsigned int val = *tp * 10 + (unsigned int)(pch - digits); + + if(saw_digit && *tp == 0) + return (0); + if(val > 255) + return (0); + *tp = (unsigned char)val; + if(!saw_digit) { + if(++octets > 4) + return (0); + saw_digit = 1; + } + } + else if(ch == '.' && saw_digit) { + if(octets == 4) + return (0); + *++tp = 0; + saw_digit = 0; + } + else + return (0); + } + if(octets < 4) + return (0); + memcpy(dst, tmp, INADDRSZ); + return (1); +} + +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton6(const char *src, unsigned char *dst) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp; + const char *curtok; + int ch, saw_xdigit; + size_t val; + + memset((tp = tmp), 0, IN6ADDRSZ); + endp = tp + IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if(*src == ':') + if(*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while((ch = *src++) != '\0') { + const char *xdigits; + const char *pch; + + pch = strchr((xdigits = xdigits_l), ch); + if(!pch) + pch = strchr((xdigits = xdigits_u), ch); + if(pch) { + val <<= 4; + val |= (pch - xdigits); + if(++saw_xdigit > 4) + return (0); + continue; + } + if(ch == ':') { + curtok = src; + if(!saw_xdigit) { + if(colonp) + return (0); + colonp = tp; + continue; + } + if(tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char) ((val >> 8) & 0xff); + *tp++ = (unsigned char) (val & 0xff); + saw_xdigit = 0; + val = 0; + continue; + } + if(ch == '.' && ((tp + INADDRSZ) <= endp) && + inet_pton4(curtok, tp) > 0) { + tp += INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if(saw_xdigit) { + if(tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char) ((val >> 8) & 0xff); + *tp++ = (unsigned char) (val & 0xff); + } + if(colonp) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const ssize_t n = tp - colonp; + ssize_t i; + + if(tp == endp) + return (0); + for(i = 1; i <= n; i++) { + *(endp - i) = *(colonp + n - i); + *(colonp + n - i) = 0; + } + tp = endp; + } + if(tp != endp) + return (0); + memcpy(dst, tmp, IN6ADDRSZ); + return (1); +} + +#endif /* HAVE_INET_PTON */ diff --git a/Utilities/cmcurl/lib/inet_pton.h b/Utilities/cmcurl/lib/inet_pton.h new file mode 100644 index 0000000..82fde7e --- /dev/null +++ b/Utilities/cmcurl/lib/inet_pton.h @@ -0,0 +1,41 @@ +#ifndef HEADER_CURL_INET_PTON_H +#define HEADER_CURL_INET_PTON_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +int Curl_inet_pton(int, const char *, void *); + +#ifdef HAVE_INET_PTON +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#elif defined(HAVE_WS2TCPIP_H) +/* inet_pton() exists in Vista or later */ +#include <ws2tcpip.h> +#endif +#define Curl_inet_pton(x,y,z) inet_pton(x,y,z) +#endif + +#endif /* HEADER_CURL_INET_PTON_H */ diff --git a/Utilities/cmcurl/lib/krb5.c b/Utilities/cmcurl/lib/krb5.c new file mode 100644 index 0000000..a1102e5 --- /dev/null +++ b/Utilities/cmcurl/lib/krb5.c @@ -0,0 +1,902 @@ +/* GSSAPI/krb5 support for FTP - loosely based on old krb4.c + * + * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * Copyright (C) Daniel Stenberg + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. */ + +#include "curl_setup.h" + +#if defined(HAVE_GSSAPI) && !defined(CURL_DISABLE_FTP) + +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#include "urldata.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "curl_base64.h" +#include "ftp.h" +#include "curl_gssapi.h" +#include "sendf.h" +#include "curl_krb5.h" +#include "warnless.h" +#include "strcase.h" +#include "strdup.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +static CURLcode ftpsend(struct Curl_easy *data, struct connectdata *conn, + const char *cmd) +{ + ssize_t bytes_written; +#define SBUF_SIZE 1024 + char s[SBUF_SIZE]; + size_t write_len; + char *sptr = s; + CURLcode result = CURLE_OK; +#ifdef HAVE_GSSAPI + unsigned char data_sec = conn->data_prot; +#endif + + if(!cmd) + return CURLE_BAD_FUNCTION_ARGUMENT; + + write_len = strlen(cmd); + if(!write_len || write_len > (sizeof(s) -3)) + return CURLE_BAD_FUNCTION_ARGUMENT; + + memcpy(&s, cmd, write_len); + strcpy(&s[write_len], "\r\n"); /* append a trailing CRLF */ + write_len += 2; + bytes_written = 0; + + for(;;) { +#ifdef HAVE_GSSAPI + conn->data_prot = PROT_CMD; +#endif + result = Curl_nwrite(data, FIRSTSOCKET, sptr, write_len, + &bytes_written); +#ifdef HAVE_GSSAPI + DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST); + conn->data_prot = data_sec; +#endif + + if(result) + break; + + Curl_debug(data, CURLINFO_HEADER_OUT, sptr, (size_t)bytes_written); + + if(bytes_written != (ssize_t)write_len) { + write_len -= bytes_written; + sptr += bytes_written; + } + else + break; + } + + return result; +} + +static int +krb5_init(void *app_data) +{ + gss_ctx_id_t *context = app_data; + /* Make sure our context is initialized for krb5_end. */ + *context = GSS_C_NO_CONTEXT; + return 0; +} + +static int +krb5_check_prot(void *app_data, int level) +{ + (void)app_data; /* unused */ + if(level == PROT_CONFIDENTIAL) + return -1; + return 0; +} + +static int +krb5_decode(void *app_data, void *buf, int len, + int level UNUSED_PARAM, + struct connectdata *conn UNUSED_PARAM) +{ + gss_ctx_id_t *context = app_data; + OM_uint32 maj, min; + gss_buffer_desc enc, dec; + + (void)level; + (void)conn; + + enc.value = buf; + enc.length = len; + maj = gss_unwrap(&min, *context, &enc, &dec, NULL, NULL); + if(maj != GSS_S_COMPLETE) + return -1; + + memcpy(buf, dec.value, dec.length); + len = curlx_uztosi(dec.length); + gss_release_buffer(&min, &dec); + + return len; +} + +static int +krb5_encode(void *app_data, const void *from, int length, int level, void **to) +{ + gss_ctx_id_t *context = app_data; + gss_buffer_desc dec, enc; + OM_uint32 maj, min; + int state; + int len; + + /* NOTE that the cast is safe, neither of the krb5, gnu gss and heimdal + * libraries modify the input buffer in gss_wrap() + */ + dec.value = (void *)from; + dec.length = length; + maj = gss_wrap(&min, *context, + level == PROT_PRIVATE, + GSS_C_QOP_DEFAULT, + &dec, &state, &enc); + + if(maj != GSS_S_COMPLETE) + return -1; + + /* malloc a new buffer, in case gss_release_buffer doesn't work as + expected */ + *to = malloc(enc.length); + if(!*to) + return -1; + memcpy(*to, enc.value, enc.length); + len = curlx_uztosi(enc.length); + gss_release_buffer(&min, &enc); + return len; +} + +static int +krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) +{ + int ret = AUTH_OK; + char *p; + const char *host = conn->host.name; + ssize_t nread; + curl_socklen_t l = sizeof(conn->local_addr); + CURLcode result; + const char *service = data->set.str[STRING_SERVICE_NAME] ? + data->set.str[STRING_SERVICE_NAME] : + "ftp"; + const char *srv_host = "host"; + gss_buffer_desc input_buffer, output_buffer, _gssresp, *gssresp; + OM_uint32 maj, min; + gss_name_t gssname; + gss_ctx_id_t *context = app_data; + struct gss_channel_bindings_struct chan; + size_t base64_sz = 0; + struct sockaddr_in *remote_addr = + (struct sockaddr_in *)(void *)&conn->remote_addr->sa_addr; + char *stringp; + + if(getsockname(conn->sock[FIRSTSOCKET], + (struct sockaddr *)&conn->local_addr, &l) < 0) + perror("getsockname()"); + + chan.initiator_addrtype = GSS_C_AF_INET; + chan.initiator_address.length = l - 4; + chan.initiator_address.value = &conn->local_addr.sin_addr.s_addr; + chan.acceptor_addrtype = GSS_C_AF_INET; + chan.acceptor_address.length = l - 4; + chan.acceptor_address.value = &remote_addr->sin_addr.s_addr; + chan.application_data.length = 0; + chan.application_data.value = NULL; + + /* this loop will execute twice (once for service, once for host) */ + for(;;) { + /* this really shouldn't be repeated here, but can't help it */ + if(service == srv_host) { + result = ftpsend(data, conn, "AUTH GSSAPI"); + if(result) + return -2; + + if(Curl_GetFTPResponse(data, &nread, NULL)) + return -1; + + if(data->state.buffer[0] != '3') + return -1; + } + + stringp = aprintf("%s@%s", service, host); + if(!stringp) + return -2; + + input_buffer.value = stringp; + input_buffer.length = strlen(stringp); + maj = gss_import_name(&min, &input_buffer, GSS_C_NT_HOSTBASED_SERVICE, + &gssname); + free(stringp); + if(maj != GSS_S_COMPLETE) { + gss_release_name(&min, &gssname); + if(service == srv_host) { + failf(data, "Error importing service name %s@%s", service, host); + return AUTH_ERROR; + } + service = srv_host; + continue; + } + /* We pass NULL as |output_name_type| to avoid a leak. */ + gss_display_name(&min, gssname, &output_buffer, NULL); + infof(data, "Trying against %s", (char *)output_buffer.value); + gssresp = GSS_C_NO_BUFFER; + *context = GSS_C_NO_CONTEXT; + + do { + /* Release the buffer at each iteration to avoid leaking: the first time + we are releasing the memory from gss_display_name. The last item is + taken care by a final gss_release_buffer. */ + gss_release_buffer(&min, &output_buffer); + ret = AUTH_OK; + maj = Curl_gss_init_sec_context(data, + &min, + context, + gssname, + &Curl_krb5_mech_oid, + &chan, + gssresp, + &output_buffer, + TRUE, + NULL); + + if(gssresp) { + free(_gssresp.value); + gssresp = NULL; + } + + if(GSS_ERROR(maj)) { + infof(data, "Error creating security context"); + ret = AUTH_ERROR; + break; + } + + if(output_buffer.length) { + char *cmd; + + result = Curl_base64_encode((char *)output_buffer.value, + output_buffer.length, &p, &base64_sz); + if(result) { + infof(data, "base64-encoding: %s", curl_easy_strerror(result)); + ret = AUTH_ERROR; + break; + } + + cmd = aprintf("ADAT %s", p); + if(cmd) + result = ftpsend(data, conn, cmd); + else + result = CURLE_OUT_OF_MEMORY; + + free(p); + free(cmd); + + if(result) { + ret = -2; + break; + } + + if(Curl_GetFTPResponse(data, &nread, NULL)) { + ret = -1; + break; + } + + if(data->state.buffer[0] != '2' && data->state.buffer[0] != '3') { + infof(data, "Server didn't accept auth data"); + ret = AUTH_ERROR; + break; + } + + _gssresp.value = NULL; /* make sure it is initialized */ + p = data->state.buffer + 4; + p = strstr(p, "ADAT="); + if(p) { + result = Curl_base64_decode(p + 5, + (unsigned char **)&_gssresp.value, + &_gssresp.length); + if(result) { + failf(data, "base64-decoding: %s", curl_easy_strerror(result)); + ret = AUTH_CONTINUE; + break; + } + } + + gssresp = &_gssresp; + } + } while(maj == GSS_S_CONTINUE_NEEDED); + + gss_release_name(&min, &gssname); + gss_release_buffer(&min, &output_buffer); + + if(gssresp) + free(_gssresp.value); + + if(ret == AUTH_OK || service == srv_host) + return ret; + + service = srv_host; + } + return ret; +} + +static void krb5_end(void *app_data) +{ + OM_uint32 min; + gss_ctx_id_t *context = app_data; + if(*context != GSS_C_NO_CONTEXT) { + OM_uint32 maj = gss_delete_sec_context(&min, context, GSS_C_NO_BUFFER); + (void)maj; + DEBUGASSERT(maj == GSS_S_COMPLETE); + } +} + +static const struct Curl_sec_client_mech Curl_krb5_client_mech = { + "GSSAPI", + sizeof(gss_ctx_id_t), + krb5_init, + krb5_auth, + krb5_end, + krb5_check_prot, + + krb5_encode, + krb5_decode +}; + +static const struct { + unsigned char level; + const char *name; +} level_names[] = { + { PROT_CLEAR, "clear" }, + { PROT_SAFE, "safe" }, + { PROT_CONFIDENTIAL, "confidential" }, + { PROT_PRIVATE, "private" } +}; + +static unsigned char name_to_level(const char *name) +{ + int i; + for(i = 0; i < (int)sizeof(level_names)/(int)sizeof(level_names[0]); i++) + if(curl_strequal(name, level_names[i].name)) + return level_names[i].level; + return PROT_NONE; +} + +/* Convert a protocol |level| to its char representation. + We take an int to catch programming mistakes. */ +static char level_to_char(int level) +{ + switch(level) { + case PROT_CLEAR: + return 'C'; + case PROT_SAFE: + return 'S'; + case PROT_CONFIDENTIAL: + return 'E'; + case PROT_PRIVATE: + return 'P'; + case PROT_CMD: + /* Fall through */ + default: + /* Those 2 cases should not be reached! */ + break; + } + DEBUGASSERT(0); + /* Default to the most secure alternative. */ + return 'P'; +} + +/* Send an FTP command defined by |message| and the optional arguments. The + function returns the ftp_code. If an error occurs, -1 is returned. */ +static int ftp_send_command(struct Curl_easy *data, const char *message, ...) +{ + int ftp_code; + ssize_t nread = 0; + va_list args; + char print_buffer[50]; + + va_start(args, message); + mvsnprintf(print_buffer, sizeof(print_buffer), message, args); + va_end(args); + + if(ftpsend(data, data->conn, print_buffer)) { + ftp_code = -1; + } + else { + if(Curl_GetFTPResponse(data, &nread, &ftp_code)) + ftp_code = -1; + } + + (void)nread; /* Unused */ + return ftp_code; +} + +/* Read |len| from the socket |fd| and store it in |to|. Return a CURLcode + saying whether an error occurred or CURLE_OK if |len| was read. */ +static CURLcode +socket_read(struct Curl_easy *data, int sockindex, void *to, size_t len) +{ + char *to_p = to; + CURLcode result; + ssize_t nread = 0; + + while(len > 0) { + nread = Curl_conn_recv(data, sockindex, to_p, len, &result); + if(nread > 0) { + len -= nread; + to_p += nread; + } + else { + if(result == CURLE_AGAIN) + continue; + return result; + } + } + return CURLE_OK; +} + + +/* Write |len| bytes from the buffer |to| to the socket |fd|. Return a + CURLcode saying whether an error occurred or CURLE_OK if |len| was + written. */ +static CURLcode +socket_write(struct Curl_easy *data, int sockindex, const void *to, + size_t len) +{ + const char *to_p = to; + CURLcode result; + ssize_t written; + + while(len > 0) { + written = Curl_conn_send(data, sockindex, to_p, len, &result); + if(written > 0) { + len -= written; + to_p += written; + } + else { + if(result == CURLE_AGAIN) + continue; + return result; + } + } + return CURLE_OK; +} + +static CURLcode read_data(struct Curl_easy *data, int sockindex, + struct krb5buffer *buf) +{ + struct connectdata *conn = data->conn; + int len; + CURLcode result; + int nread; + + result = socket_read(data, sockindex, &len, sizeof(len)); + if(result) + return result; + + if(len) { + /* only realloc if there was a length */ + len = ntohl(len); + if(len > CURL_MAX_INPUT_LENGTH) + len = 0; + else + buf->data = Curl_saferealloc(buf->data, len); + } + if(!len || !buf->data) + return CURLE_OUT_OF_MEMORY; + + result = socket_read(data, sockindex, buf->data, len); + if(result) + return result; + nread = conn->mech->decode(conn->app_data, buf->data, len, + conn->data_prot, conn); + if(nread < 0) + return CURLE_RECV_ERROR; + buf->size = (size_t)nread; + buf->index = 0; + return CURLE_OK; +} + +static size_t +buffer_read(struct krb5buffer *buf, void *data, size_t len) +{ + if(buf->size - buf->index < len) + len = buf->size - buf->index; + memcpy(data, (char *)buf->data + buf->index, len); + buf->index += len; + return len; +} + +/* Matches Curl_recv signature */ +static ssize_t sec_recv(struct Curl_easy *data, int sockindex, + char *buffer, size_t len, CURLcode *err) +{ + size_t bytes_read; + size_t total_read = 0; + struct connectdata *conn = data->conn; + + *err = CURLE_OK; + + /* Handle clear text response. */ + if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR) + return Curl_conn_recv(data, sockindex, buffer, len, err); + + if(conn->in_buffer.eof_flag) { + conn->in_buffer.eof_flag = 0; + return 0; + } + + bytes_read = buffer_read(&conn->in_buffer, buffer, len); + len -= bytes_read; + total_read += bytes_read; + buffer += bytes_read; + + while(len > 0) { + if(read_data(data, sockindex, &conn->in_buffer)) + return -1; + if(conn->in_buffer.size == 0) { + if(bytes_read > 0) + conn->in_buffer.eof_flag = 1; + return bytes_read; + } + bytes_read = buffer_read(&conn->in_buffer, buffer, len); + len -= bytes_read; + total_read += bytes_read; + buffer += bytes_read; + } + return total_read; +} + +/* Send |length| bytes from |from| to the |fd| socket taking care of encoding + and negotiating with the server. |from| can be NULL. */ +static void do_sec_send(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t fd, const char *from, int length) +{ + int bytes, htonl_bytes; /* 32-bit integers for htonl */ + char *buffer = NULL; + char *cmd_buffer; + size_t cmd_size = 0; + CURLcode error; + enum protection_level prot_level = conn->data_prot; + bool iscmd = (prot_level == PROT_CMD)?TRUE:FALSE; + + DEBUGASSERT(prot_level > PROT_NONE && prot_level < PROT_LAST); + + if(iscmd) { + if(!strncmp(from, "PASS ", 5) || !strncmp(from, "ACCT ", 5)) + prot_level = PROT_PRIVATE; + else + prot_level = conn->command_prot; + } + bytes = conn->mech->encode(conn->app_data, from, length, prot_level, + (void **)&buffer); + if(!buffer || bytes <= 0) + return; /* error */ + + if(iscmd) { + error = Curl_base64_encode(buffer, curlx_sitouz(bytes), + &cmd_buffer, &cmd_size); + if(error) { + free(buffer); + return; /* error */ + } + if(cmd_size > 0) { + static const char *enc = "ENC "; + static const char *mic = "MIC "; + if(prot_level == PROT_PRIVATE) + socket_write(data, fd, enc, 4); + else + socket_write(data, fd, mic, 4); + + socket_write(data, fd, cmd_buffer, cmd_size); + socket_write(data, fd, "\r\n", 2); + infof(data, "Send: %s%s", prot_level == PROT_PRIVATE?enc:mic, + cmd_buffer); + free(cmd_buffer); + } + } + else { + htonl_bytes = htonl(bytes); + socket_write(data, fd, &htonl_bytes, sizeof(htonl_bytes)); + socket_write(data, fd, buffer, curlx_sitouz(bytes)); + } + free(buffer); +} + +static ssize_t sec_write(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t fd, const char *buffer, size_t length) +{ + ssize_t tx = 0, len = conn->buffer_size; + + if(len <= 0) + len = length; + while(length) { + if(length < (size_t)len) + len = length; + + do_sec_send(data, conn, fd, buffer, curlx_sztosi(len)); + length -= len; + buffer += len; + tx += len; + } + return tx; +} + +/* Matches Curl_send signature */ +static ssize_t sec_send(struct Curl_easy *data, int sockindex, + const void *buffer, size_t len, CURLcode *err) +{ + struct connectdata *conn = data->conn; + curl_socket_t fd = conn->sock[sockindex]; + *err = CURLE_OK; + return sec_write(data, conn, fd, buffer, len); +} + +int Curl_sec_read_msg(struct Curl_easy *data, struct connectdata *conn, + char *buffer, enum protection_level level) +{ + /* decoded_len should be size_t or ssize_t but conn->mech->decode returns an + int */ + int decoded_len; + char *buf; + int ret_code = 0; + size_t decoded_sz = 0; + CURLcode error; + + (void) data; + + if(!conn->mech) + /* not initialized, return error */ + return -1; + + DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); + + error = Curl_base64_decode(buffer + 4, (unsigned char **)&buf, &decoded_sz); + if(error || decoded_sz == 0) + return -1; + + if(decoded_sz > (size_t)INT_MAX) { + free(buf); + return -1; + } + decoded_len = curlx_uztosi(decoded_sz); + + decoded_len = conn->mech->decode(conn->app_data, buf, decoded_len, + level, conn); + if(decoded_len <= 0) { + free(buf); + return -1; + } + + { + buf[decoded_len] = '\n'; + Curl_debug(data, CURLINFO_HEADER_IN, buf, decoded_len + 1); + } + + buf[decoded_len] = '\0'; + if(decoded_len <= 3) + /* suspiciously short */ + return 0; + + if(buf[3] != '-') + ret_code = atoi(buf); + + if(buf[decoded_len - 1] == '\n') + buf[decoded_len - 1] = '\0'; + strcpy(buffer, buf); + free(buf); + return ret_code; +} + +static int sec_set_protection_level(struct Curl_easy *data) +{ + int code; + struct connectdata *conn = data->conn; + unsigned char level = conn->request_data_prot; + + DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); + + if(!conn->sec_complete) { + infof(data, "Trying to change the protection level after the" + " completion of the data exchange."); + return -1; + } + + /* Bail out if we try to set up the same level */ + if(conn->data_prot == level) + return 0; + + if(level) { + char *pbsz; + unsigned int buffer_size = 1 << 20; /* 1048576 */ + + code = ftp_send_command(data, "PBSZ %u", buffer_size); + if(code < 0) + return -1; + + if(code/100 != 2) { + failf(data, "Failed to set the protection's buffer size."); + return -1; + } + conn->buffer_size = buffer_size; + + pbsz = strstr(data->state.buffer, "PBSZ="); + if(pbsz) { + /* stick to default value if the check fails */ + if(!strncmp(pbsz, "PBSZ=", 5) && ISDIGIT(pbsz[5])) + buffer_size = atoi(&pbsz[5]); + if(buffer_size < conn->buffer_size) + conn->buffer_size = buffer_size; + } + } + + /* Now try to negotiate the protection level. */ + code = ftp_send_command(data, "PROT %c", level_to_char(level)); + + if(code < 0) + return -1; + + if(code/100 != 2) { + failf(data, "Failed to set the protection level."); + return -1; + } + + conn->data_prot = level; + if(level == PROT_PRIVATE) + conn->command_prot = level; + + return 0; +} + +int +Curl_sec_request_prot(struct connectdata *conn, const char *level) +{ + unsigned char l = name_to_level(level); + if(l == PROT_NONE) + return -1; + DEBUGASSERT(l > PROT_NONE && l < PROT_LAST); + conn->request_data_prot = l; + return 0; +} + +static CURLcode choose_mech(struct Curl_easy *data, struct connectdata *conn) +{ + int ret; + void *tmp_allocation; + const struct Curl_sec_client_mech *mech = &Curl_krb5_client_mech; + + tmp_allocation = realloc(conn->app_data, mech->size); + if(!tmp_allocation) { + failf(data, "Failed realloc of size %zu", mech->size); + mech = NULL; + return CURLE_OUT_OF_MEMORY; + } + conn->app_data = tmp_allocation; + + if(mech->init) { + ret = mech->init(conn->app_data); + if(ret) { + infof(data, "Failed initialization for %s. Skipping it.", + mech->name); + return CURLE_FAILED_INIT; + } + } + + infof(data, "Trying mechanism %s...", mech->name); + ret = ftp_send_command(data, "AUTH %s", mech->name); + if(ret < 0) + return CURLE_COULDNT_CONNECT; + + if(ret/100 != 3) { + switch(ret) { + case 504: + infof(data, "Mechanism %s is not supported by the server (server " + "returned ftp code: 504).", mech->name); + break; + case 534: + infof(data, "Mechanism %s was rejected by the server (server returned " + "ftp code: 534).", mech->name); + break; + default: + if(ret/100 == 5) { + infof(data, "server does not support the security extensions"); + return CURLE_USE_SSL_FAILED; + } + break; + } + return CURLE_LOGIN_DENIED; + } + + /* Authenticate */ + ret = mech->auth(conn->app_data, data, conn); + + if(ret != AUTH_CONTINUE) { + if(ret != AUTH_OK) { + /* Mechanism has dumped the error to stderr, don't error here. */ + return CURLE_USE_SSL_FAILED; + } + DEBUGASSERT(ret == AUTH_OK); + + conn->mech = mech; + conn->sec_complete = 1; + conn->recv[FIRSTSOCKET] = sec_recv; + conn->send[FIRSTSOCKET] = sec_send; + conn->recv[SECONDARYSOCKET] = sec_recv; + conn->send[SECONDARYSOCKET] = sec_send; + conn->command_prot = PROT_SAFE; + /* Set the requested protection level */ + /* BLOCKING */ + (void)sec_set_protection_level(data); + } + + return CURLE_OK; +} + +CURLcode +Curl_sec_login(struct Curl_easy *data, struct connectdata *conn) +{ + return choose_mech(data, conn); +} + + +void +Curl_sec_end(struct connectdata *conn) +{ + if(conn->mech && conn->mech->end) + conn->mech->end(conn->app_data); + free(conn->app_data); + conn->app_data = NULL; + if(conn->in_buffer.data) { + free(conn->in_buffer.data); + conn->in_buffer.data = NULL; + conn->in_buffer.size = 0; + conn->in_buffer.index = 0; + conn->in_buffer.eof_flag = 0; + } + conn->sec_complete = 0; + conn->data_prot = PROT_CLEAR; + conn->mech = NULL; +} + +#endif /* HAVE_GSSAPI && !CURL_DISABLE_FTP */ diff --git a/Utilities/cmcurl/lib/ldap.c b/Utilities/cmcurl/lib/ldap.c new file mode 100644 index 0000000..eb5fe795 --- /dev/null +++ b/Utilities/cmcurl/lib/ldap.c @@ -0,0 +1,1107 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_LDAP) && !defined(USE_OPENLDAP) + +/* + * Notice that USE_OPENLDAP is only a source code selection switch. When + * libcurl is built with USE_OPENLDAP defined the libcurl source code that + * gets compiled is the code from openldap.c, otherwise the code that gets + * compiled is the code from ldap.c. + * + * When USE_OPENLDAP is defined a recent version of the OpenLDAP library + * might be required for compilation and runtime. In order to use ancient + * OpenLDAP library versions, USE_OPENLDAP shall not be defined. + */ + +/* Wincrypt must be included before anything that could include OpenSSL. */ +#if defined(USE_WIN32_CRYPTO) +#include <wincrypt.h> +/* Undefine wincrypt conflicting symbols for BoringSSL. */ +#undef X509_NAME +#undef X509_EXTENSIONS +#undef PKCS7_ISSUER_AND_SERIAL +#undef PKCS7_SIGNER_INFO +#undef OCSP_REQUEST +#undef OCSP_RESPONSE +#endif + +#ifdef USE_WIN32_LDAP /* Use Windows LDAP implementation. */ +# ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4201) +# endif +# include <subauth.h> /* for [P]UNICODE_STRING */ +# ifdef _MSC_VER +# pragma warning(pop) +# endif +# include <winldap.h> +# ifndef LDAP_VENDOR_NAME +# error Your Platform SDK is NOT sufficient for LDAP support! \ + Update your Platform SDK, or disable LDAP support! +# else +# include <winber.h> +# endif +#else +# define LDAP_DEPRECATED 1 /* Be sure ldap_init() is defined. */ +# ifdef HAVE_LBER_H +# include <lber.h> +# endif +# include <ldap.h> +# if (defined(HAVE_LDAP_SSL) && defined(HAVE_LDAP_SSL_H)) +# include <ldap_ssl.h> +# endif /* HAVE_LDAP_SSL && HAVE_LDAP_SSL_H */ +#endif + +#include "urldata.h" +#include <curl/curl.h> +#include "sendf.h" +#include "escape.h" +#include "progress.h" +#include "transfer.h" +#include "strcase.h" +#include "strtok.h" +#include "curl_ldap.h" +#include "curl_multibyte.h" +#include "curl_base64.h" +#include "connect.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifndef HAVE_LDAP_URL_PARSE + +/* Use our own implementation. */ + +struct ldap_urldesc { + char *lud_host; + int lud_port; +#if defined(USE_WIN32_LDAP) + TCHAR *lud_dn; + TCHAR **lud_attrs; +#else + char *lud_dn; + char **lud_attrs; +#endif + int lud_scope; +#if defined(USE_WIN32_LDAP) + TCHAR *lud_filter; +#else + char *lud_filter; +#endif + char **lud_exts; + size_t lud_attrs_dups; /* how many were dup'ed, this field is not in the + "real" struct so can only be used in code + without HAVE_LDAP_URL_PARSE defined */ +}; + +#undef LDAPURLDesc +#define LDAPURLDesc struct ldap_urldesc + +static int _ldap_url_parse(struct Curl_easy *data, + const struct connectdata *conn, + LDAPURLDesc **ludp); +static void _ldap_free_urldesc(LDAPURLDesc *ludp); + +#undef ldap_free_urldesc +#define ldap_free_urldesc _ldap_free_urldesc +#endif + +#ifdef DEBUG_LDAP + #define LDAP_TRACE(x) do { \ + _ldap_trace("%u: ", __LINE__); \ + _ldap_trace x; \ + } while(0) + + static void _ldap_trace(const char *fmt, ...); +#else + #define LDAP_TRACE(x) Curl_nop_stmt +#endif + +#if defined(USE_WIN32_LDAP) && defined(ldap_err2string) +/* Use ansi error strings in UNICODE builds */ +#undef ldap_err2string +#define ldap_err2string ldap_err2stringA +#endif + +#if defined(USE_WIN32_LDAP) && defined(_MSC_VER) && (_MSC_VER <= 1600) +/* Workaround for warning: + 'type cast' : conversion from 'int' to 'void *' of greater size */ +#undef LDAP_OPT_ON +#undef LDAP_OPT_OFF +#define LDAP_OPT_ON ((void *)(size_t)1) +#define LDAP_OPT_OFF ((void *)(size_t)0) +#endif + +static CURLcode ldap_do(struct Curl_easy *data, bool *done); + +/* + * LDAP protocol handler. + */ + +const struct Curl_handler Curl_handler_ldap = { + "LDAP", /* scheme */ + ZERO_NULL, /* setup_connection */ + ldap_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_LDAP, /* defport */ + CURLPROTO_LDAP, /* protocol */ + CURLPROTO_LDAP, /* family */ + PROTOPT_NONE /* flags */ +}; + +#ifdef HAVE_LDAP_SSL +/* + * LDAPS protocol handler. + */ + +const struct Curl_handler Curl_handler_ldaps = { + "LDAPS", /* scheme */ + ZERO_NULL, /* setup_connection */ + ldap_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_LDAPS, /* defport */ + CURLPROTO_LDAPS, /* protocol */ + CURLPROTO_LDAP, /* family */ + PROTOPT_SSL /* flags */ +}; +#endif + +#if defined(USE_WIN32_LDAP) + +#if defined(USE_WINDOWS_SSPI) +static int ldap_win_bind_auth(LDAP *server, const char *user, + const char *passwd, unsigned long authflags) +{ + ULONG method = 0; + SEC_WINNT_AUTH_IDENTITY cred; + int rc = LDAP_AUTH_METHOD_NOT_SUPPORTED; + + memset(&cred, 0, sizeof(cred)); + +#if defined(USE_SPNEGO) + if(authflags & CURLAUTH_NEGOTIATE) { + method = LDAP_AUTH_NEGOTIATE; + } + else +#endif +#if defined(USE_NTLM) + if(authflags & CURLAUTH_NTLM) { + method = LDAP_AUTH_NTLM; + } + else +#endif +#if !defined(CURL_DISABLE_DIGEST_AUTH) + if(authflags & CURLAUTH_DIGEST) { + method = LDAP_AUTH_DIGEST; + } + else +#endif + { + /* required anyway if one of upper preprocessor definitions enabled */ + } + + if(method && user && passwd) { + rc = Curl_create_sspi_identity(user, passwd, &cred); + if(!rc) { + rc = ldap_bind_s(server, NULL, (TCHAR *)&cred, method); + Curl_sspi_free_identity(&cred); + } + } + else { + /* proceed with current user credentials */ + method = LDAP_AUTH_NEGOTIATE; + rc = ldap_bind_s(server, NULL, NULL, method); + } + return rc; +} +#endif /* #if defined(USE_WINDOWS_SSPI) */ + +static int ldap_win_bind(struct Curl_easy *data, LDAP *server, + const char *user, const char *passwd) +{ + int rc = LDAP_INVALID_CREDENTIALS; + + PTCHAR inuser = NULL; + PTCHAR inpass = NULL; + + if(user && passwd && (data->set.httpauth & CURLAUTH_BASIC)) { + inuser = curlx_convert_UTF8_to_tchar((char *) user); + inpass = curlx_convert_UTF8_to_tchar((char *) passwd); + + rc = ldap_simple_bind_s(server, inuser, inpass); + + curlx_unicodefree(inuser); + curlx_unicodefree(inpass); + } +#if defined(USE_WINDOWS_SSPI) + else { + rc = ldap_win_bind_auth(server, user, passwd, data->set.httpauth); + } +#endif + + return rc; +} +#endif /* #if defined(USE_WIN32_LDAP) */ + +#if defined(USE_WIN32_LDAP) +#define FREE_ON_WINLDAP(x) curlx_unicodefree(x) +#else +#define FREE_ON_WINLDAP(x) +#endif + + +static CURLcode ldap_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + int rc = 0; + LDAP *server = NULL; + LDAPURLDesc *ludp = NULL; + LDAPMessage *ldapmsg = NULL; + LDAPMessage *entryIterator; + int num = 0; + struct connectdata *conn = data->conn; + int ldap_proto = LDAP_VERSION3; + int ldap_ssl = 0; + char *val_b64 = NULL; + size_t val_b64_sz = 0; +#ifdef LDAP_OPT_NETWORK_TIMEOUT + struct timeval ldap_timeout = {10, 0}; /* 10 sec connection/search timeout */ +#endif +#if defined(USE_WIN32_LDAP) + TCHAR *host = NULL; +#else + char *host = NULL; +#endif + char *user = NULL; + char *passwd = NULL; + + *done = TRUE; /* unconditionally */ + infof(data, "LDAP local: LDAP Vendor = %s ; LDAP Version = %d", + LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION); + infof(data, "LDAP local: %s", data->state.url); + +#ifdef HAVE_LDAP_URL_PARSE + rc = ldap_url_parse(data->state.url, &ludp); +#else + rc = _ldap_url_parse(data, conn, &ludp); +#endif + if(rc) { + failf(data, "Bad LDAP URL: %s", ldap_err2string(rc)); + result = CURLE_URL_MALFORMAT; + goto quit; + } + + /* Get the URL scheme (either ldap or ldaps) */ + if(conn->given->flags & PROTOPT_SSL) + ldap_ssl = 1; + infof(data, "LDAP local: trying to establish %s connection", + ldap_ssl ? "encrypted" : "cleartext"); + +#if defined(USE_WIN32_LDAP) + host = curlx_convert_UTF8_to_tchar(conn->host.name); + if(!host) { + result = CURLE_OUT_OF_MEMORY; + + goto quit; + } +#else + host = conn->host.name; +#endif + + if(data->state.aptr.user) { + user = conn->user; + passwd = conn->passwd; + } + +#ifdef LDAP_OPT_NETWORK_TIMEOUT + ldap_set_option(NULL, LDAP_OPT_NETWORK_TIMEOUT, &ldap_timeout); +#endif + ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); + + if(ldap_ssl) { +#ifdef HAVE_LDAP_SSL +#ifdef USE_WIN32_LDAP + /* Win32 LDAP SDK doesn't support insecure mode without CA! */ + server = ldap_sslinit(host, conn->port, 1); + ldap_set_option(server, LDAP_OPT_SSL, LDAP_OPT_ON); +#else + int ldap_option; + char *ldap_ca = conn->ssl_config.CAfile; +#if defined(CURL_HAS_NOVELL_LDAPSDK) + rc = ldapssl_client_init(NULL, NULL); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ldapssl_client_init %s", ldap_err2string(rc)); + result = CURLE_SSL_CERTPROBLEM; + goto quit; + } + if(conn->ssl_config.verifypeer) { + /* Novell SDK supports DER or BASE64 files. */ + int cert_type = LDAPSSL_CERT_FILETYPE_B64; + if((data->set.ssl.cert_type) && + (strcasecompare(data->set.ssl.cert_type, "DER"))) + cert_type = LDAPSSL_CERT_FILETYPE_DER; + if(!ldap_ca) { + failf(data, "LDAP local: ERROR %s CA cert not set", + (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM")); + result = CURLE_SSL_CERTPROBLEM; + goto quit; + } + infof(data, "LDAP local: using %s CA cert '%s'", + (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"), + ldap_ca); + rc = ldapssl_add_trusted_cert(ldap_ca, cert_type); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting %s CA cert: %s", + (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"), + ldap_err2string(rc)); + result = CURLE_SSL_CERTPROBLEM; + goto quit; + } + ldap_option = LDAPSSL_VERIFY_SERVER; + } + else + ldap_option = LDAPSSL_VERIFY_NONE; + rc = ldapssl_set_verify_mode(ldap_option); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting cert verify mode: %s", + ldap_err2string(rc)); + result = CURLE_SSL_CERTPROBLEM; + goto quit; + } + server = ldapssl_init(host, conn->port, 1); + if(!server) { + failf(data, "LDAP local: Cannot connect to %s:%u", + conn->host.dispname, conn->port); + result = CURLE_COULDNT_CONNECT; + goto quit; + } +#elif defined(LDAP_OPT_X_TLS) + if(conn->ssl_config.verifypeer) { + /* OpenLDAP SDK supports BASE64 files. */ + if((data->set.ssl.cert_type) && + (!strcasecompare(data->set.ssl.cert_type, "PEM"))) { + failf(data, "LDAP local: ERROR OpenLDAP only supports PEM cert-type"); + result = CURLE_SSL_CERTPROBLEM; + goto quit; + } + if(!ldap_ca) { + failf(data, "LDAP local: ERROR PEM CA cert not set"); + result = CURLE_SSL_CERTPROBLEM; + goto quit; + } + infof(data, "LDAP local: using PEM CA cert: %s", ldap_ca); + rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, ldap_ca); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting PEM CA cert: %s", + ldap_err2string(rc)); + result = CURLE_SSL_CERTPROBLEM; + goto quit; + } + ldap_option = LDAP_OPT_X_TLS_DEMAND; + } + else + ldap_option = LDAP_OPT_X_TLS_NEVER; + + rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &ldap_option); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting cert verify mode: %s", + ldap_err2string(rc)); + result = CURLE_SSL_CERTPROBLEM; + goto quit; + } + server = ldap_init(host, conn->port); + if(!server) { + failf(data, "LDAP local: Cannot connect to %s:%u", + conn->host.dispname, conn->port); + result = CURLE_COULDNT_CONNECT; + goto quit; + } + ldap_option = LDAP_OPT_X_TLS_HARD; + rc = ldap_set_option(server, LDAP_OPT_X_TLS, &ldap_option); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting SSL/TLS mode: %s", + ldap_err2string(rc)); + result = CURLE_SSL_CERTPROBLEM; + goto quit; + } +/* + rc = ldap_start_tls_s(server, NULL, NULL); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR starting SSL/TLS mode: %s", + ldap_err2string(rc)); + result = CURLE_SSL_CERTPROBLEM; + goto quit; + } +*/ +#else + /* we should probably never come up to here since configure + should check in first place if we can support LDAP SSL/TLS */ + failf(data, "LDAP local: SSL/TLS not supported with this version " + "of the OpenLDAP toolkit\n"); + result = CURLE_SSL_CERTPROBLEM; + goto quit; +#endif +#endif +#endif /* CURL_LDAP_USE_SSL */ + } + else if(data->set.use_ssl > CURLUSESSL_TRY) { + failf(data, "LDAP local: explicit TLS not supported"); + result = CURLE_NOT_BUILT_IN; + goto quit; + } + else { + server = ldap_init(host, conn->port); + if(!server) { + failf(data, "LDAP local: Cannot connect to %s:%u", + conn->host.dispname, conn->port); + result = CURLE_COULDNT_CONNECT; + goto quit; + } + } +#ifdef USE_WIN32_LDAP + ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); + rc = ldap_win_bind(data, server, user, passwd); +#else + rc = ldap_simple_bind_s(server, user, passwd); +#endif + if(!ldap_ssl && rc) { + ldap_proto = LDAP_VERSION2; + ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); +#ifdef USE_WIN32_LDAP + rc = ldap_win_bind(data, server, user, passwd); +#else + rc = ldap_simple_bind_s(server, user, passwd); +#endif + } + if(rc) { +#ifdef USE_WIN32_LDAP + failf(data, "LDAP local: bind via ldap_win_bind %s", + ldap_err2string(rc)); +#else + failf(data, "LDAP local: bind via ldap_simple_bind_s %s", + ldap_err2string(rc)); +#endif + result = CURLE_LDAP_CANNOT_BIND; + goto quit; + } + + Curl_pgrsSetDownloadCounter(data, 0); + rc = ldap_search_s(server, ludp->lud_dn, ludp->lud_scope, + ludp->lud_filter, ludp->lud_attrs, 0, &ldapmsg); + + if(rc && rc != LDAP_SIZELIMIT_EXCEEDED) { + failf(data, "LDAP remote: %s", ldap_err2string(rc)); + result = CURLE_LDAP_SEARCH_FAILED; + goto quit; + } + + for(num = 0, entryIterator = ldap_first_entry(server, ldapmsg); + entryIterator; + entryIterator = ldap_next_entry(server, entryIterator), num++) { + BerElement *ber = NULL; +#if defined(USE_WIN32_LDAP) + TCHAR *attribute; +#else + char *attribute; +#endif + int i; + + /* Get the DN and write it to the client */ + { + char *name; + size_t name_len; +#if defined(USE_WIN32_LDAP) + TCHAR *dn = ldap_get_dn(server, entryIterator); + name = curlx_convert_tchar_to_UTF8(dn); + if(!name) { + ldap_memfree(dn); + + result = CURLE_OUT_OF_MEMORY; + + goto quit; + } +#else + char *dn = name = ldap_get_dn(server, entryIterator); +#endif + name_len = strlen(name); + + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"DN: ", 4); + if(result) { + FREE_ON_WINLDAP(name); + ldap_memfree(dn); + goto quit; + } + + result = Curl_client_write(data, CLIENTWRITE_BODY, name, name_len); + if(result) { + FREE_ON_WINLDAP(name); + ldap_memfree(dn); + goto quit; + } + + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); + if(result) { + FREE_ON_WINLDAP(name); + ldap_memfree(dn); + + goto quit; + } + + FREE_ON_WINLDAP(name); + ldap_memfree(dn); + } + + /* Get the attributes and write them to the client */ + for(attribute = ldap_first_attribute(server, entryIterator, &ber); + attribute; + attribute = ldap_next_attribute(server, entryIterator, ber)) { + BerValue **vals; + size_t attr_len; +#if defined(USE_WIN32_LDAP) + char *attr = curlx_convert_tchar_to_UTF8(attribute); + if(!attr) { + if(ber) + ber_free(ber, 0); + + result = CURLE_OUT_OF_MEMORY; + + goto quit; + } +#else + char *attr = attribute; +#endif + attr_len = strlen(attr); + + vals = ldap_get_values_len(server, entryIterator, attribute); + if(vals) { + for(i = 0; (vals[i] != NULL); i++) { + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\t", 1); + if(result) { + ldap_value_free_len(vals); + FREE_ON_WINLDAP(attr); + ldap_memfree(attribute); + if(ber) + ber_free(ber, 0); + + 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); + if(ber) + ber_free(ber, 0); + + goto quit; + } + + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)": ", 2); + if(result) { + ldap_value_free_len(vals); + FREE_ON_WINLDAP(attr); + ldap_memfree(attribute); + if(ber) + ber_free(ber, 0); + + goto quit; + } + + if((attr_len > 7) && + (strcmp(";binary", attr + (attr_len - 7)) == 0)) { + /* Binary attribute, encode to base64. */ + result = Curl_base64_encode(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); + if(ber) + ber_free(ber, 0); + + goto quit; + } + + if(val_b64_sz > 0) { + result = Curl_client_write(data, CLIENTWRITE_BODY, val_b64, + val_b64_sz); + free(val_b64); + if(result) { + ldap_value_free_len(vals); + FREE_ON_WINLDAP(attr); + ldap_memfree(attribute); + if(ber) + ber_free(ber, 0); + + goto quit; + } + } + } + else { + 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); + if(ber) + ber_free(ber, 0); + + goto quit; + } + } + + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); + if(result) { + ldap_value_free_len(vals); + FREE_ON_WINLDAP(attr); + ldap_memfree(attribute); + if(ber) + ber_free(ber, 0); + + goto quit; + } + } + + /* Free memory used to store values */ + ldap_value_free_len(vals); + } + + /* Free the attribute as we are done with it */ + FREE_ON_WINLDAP(attr); + ldap_memfree(attribute); + + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); + if(result) + goto quit; + } + + if(ber) + ber_free(ber, 0); + } + +quit: + if(ldapmsg) { + ldap_msgfree(ldapmsg); + LDAP_TRACE(("Received %d entries\n", num)); + } + if(rc == LDAP_SIZELIMIT_EXCEEDED) + infof(data, "There are more than %d entries", num); + if(ludp) + ldap_free_urldesc(ludp); + if(server) + ldap_unbind_s(server); +#if defined(HAVE_LDAP_SSL) && defined(CURL_HAS_NOVELL_LDAPSDK) + if(ldap_ssl) + ldapssl_client_deinit(); +#endif /* HAVE_LDAP_SSL && CURL_HAS_NOVELL_LDAPSDK */ + + FREE_ON_WINLDAP(host); + + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + connclose(conn, "LDAP connection always disable reuse"); + + return result; +} + +#ifdef DEBUG_LDAP +static void _ldap_trace(const char *fmt, ...) +{ + static int do_trace = -1; + va_list args; + + if(do_trace == -1) { + const char *env = getenv("CURL_TRACE"); + do_trace = (env && strtol(env, NULL, 10) > 0); + } + if(!do_trace) + return; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} +#endif + +#ifndef HAVE_LDAP_URL_PARSE + +/* + * Return scope-value for a scope-string. + */ +static int str2scope(const char *p) +{ + if(strcasecompare(p, "one")) + return LDAP_SCOPE_ONELEVEL; + if(strcasecompare(p, "onetree")) + return LDAP_SCOPE_ONELEVEL; + if(strcasecompare(p, "base")) + return LDAP_SCOPE_BASE; + if(strcasecompare(p, "sub")) + return LDAP_SCOPE_SUBTREE; + if(strcasecompare(p, "subtree")) + return LDAP_SCOPE_SUBTREE; + return (-1); +} + +/* + * Split 'str' into strings separated by commas. + * Note: out[] points into 'str'. + */ +static bool split_str(char *str, char ***out, size_t *count) +{ + char **res; + char *lasts; + char *s; + size_t i; + size_t items = 1; + + s = strchr(str, ','); + while(s) { + items++; + s = strchr(++s, ','); + } + + res = calloc(items, sizeof(char *)); + if(!res) + return FALSE; + + for(i = 0, s = strtok_r(str, ",", &lasts); s && i < items; + s = strtok_r(NULL, ",", &lasts), i++) + res[i] = s; + + *out = res; + *count = items; + + return TRUE; +} + +/* + * Break apart the pieces of an LDAP URL. + * Syntax: + * ldap://<hostname>:<port>/<base_dn>?<attributes>?<scope>?<filter>?<ext> + * + * <hostname> already known from 'conn->host.name'. + * <port> already known from 'conn->remote_port'. + * extract the rest from 'data->state.path+1'. All fields are optional. + * e.g. + * ldap://<hostname>:<port>/?<attributes>?<scope>?<filter> + * yields ludp->lud_dn = "". + * + * Defined in RFC4516 section 2. + */ +static int _ldap_url_parse2(struct Curl_easy *data, + const struct connectdata *conn, LDAPURLDesc *ludp) +{ + int rc = LDAP_SUCCESS; + char *p; + char *path; + char *q = NULL; + char *query = NULL; + size_t i; + + if(!data || + !data->state.up.path || + data->state.up.path[0] != '/' || + !strncasecompare("LDAP", data->state.up.scheme, 4)) + return LDAP_INVALID_SYNTAX; + + ludp->lud_scope = LDAP_SCOPE_BASE; + ludp->lud_port = conn->remote_port; + ludp->lud_host = conn->host.name; + + /* Duplicate the path */ + p = path = strdup(data->state.up.path + 1); + if(!path) + return LDAP_NO_MEMORY; + + /* Duplicate the query if present */ + if(data->state.up.query) { + q = query = strdup(data->state.up.query); + if(!query) { + free(path); + return LDAP_NO_MEMORY; + } + } + + /* Parse the DN (Distinguished Name) */ + if(*p) { + char *dn = p; + char *unescaped; + CURLcode result; + + LDAP_TRACE(("DN '%s'\n", dn)); + + /* Unescape the DN */ + result = Curl_urldecode(dn, 0, &unescaped, NULL, REJECT_ZERO); + if(result) { + rc = LDAP_NO_MEMORY; + + goto quit; + } + +#if defined(USE_WIN32_LDAP) + /* Convert the unescaped string to a tchar */ + ludp->lud_dn = curlx_convert_UTF8_to_tchar(unescaped); + + /* Free the unescaped string as we are done with it */ + free(unescaped); + + if(!ludp->lud_dn) { + rc = LDAP_NO_MEMORY; + + goto quit; + } +#else + ludp->lud_dn = unescaped; +#endif + } + + p = q; + if(!p) + goto quit; + + /* Parse the attributes. skip "??" */ + q = strchr(p, '?'); + if(q) + *q++ = '\0'; + + if(*p) { + char **attributes; + size_t count = 0; + + /* Split the string into an array of attributes */ + if(!split_str(p, &attributes, &count)) { + rc = LDAP_NO_MEMORY; + + goto quit; + } + + /* Allocate our array (+1 for the NULL entry) */ +#if defined(USE_WIN32_LDAP) + ludp->lud_attrs = calloc(count + 1, sizeof(TCHAR *)); +#else + ludp->lud_attrs = calloc(count + 1, sizeof(char *)); +#endif + if(!ludp->lud_attrs) { + free(attributes); + + rc = LDAP_NO_MEMORY; + + goto quit; + } + + for(i = 0; i < count; i++) { + char *unescaped; + CURLcode result; + + LDAP_TRACE(("attr[%zu] '%s'\n", i, attributes[i])); + + /* Unescape the attribute */ + result = Curl_urldecode(attributes[i], 0, &unescaped, NULL, + REJECT_ZERO); + if(result) { + free(attributes); + + rc = LDAP_NO_MEMORY; + + goto quit; + } + +#if defined(USE_WIN32_LDAP) + /* Convert the unescaped string to a tchar */ + ludp->lud_attrs[i] = curlx_convert_UTF8_to_tchar(unescaped); + + /* Free the unescaped string as we are done with it */ + free(unescaped); + + if(!ludp->lud_attrs[i]) { + free(attributes); + + rc = LDAP_NO_MEMORY; + + goto quit; + } +#else + ludp->lud_attrs[i] = unescaped; +#endif + + ludp->lud_attrs_dups++; + } + + free(attributes); + } + + p = q; + if(!p) + goto quit; + + /* Parse the scope. skip "??" */ + q = strchr(p, '?'); + if(q) + *q++ = '\0'; + + if(*p) { + ludp->lud_scope = str2scope(p); + if(ludp->lud_scope == -1) { + rc = LDAP_INVALID_SYNTAX; + + goto quit; + } + LDAP_TRACE(("scope %d\n", ludp->lud_scope)); + } + + p = q; + if(!p) + goto quit; + + /* Parse the filter */ + q = strchr(p, '?'); + if(q) + *q++ = '\0'; + + if(*p) { + char *filter = p; + char *unescaped; + CURLcode result; + + LDAP_TRACE(("filter '%s'\n", filter)); + + /* Unescape the filter */ + result = Curl_urldecode(filter, 0, &unescaped, NULL, REJECT_ZERO); + if(result) { + rc = LDAP_NO_MEMORY; + + goto quit; + } + +#if defined(USE_WIN32_LDAP) + /* Convert the unescaped string to a tchar */ + ludp->lud_filter = curlx_convert_UTF8_to_tchar(unescaped); + + /* Free the unescaped string as we are done with it */ + free(unescaped); + + if(!ludp->lud_filter) { + rc = LDAP_NO_MEMORY; + + goto quit; + } +#else + ludp->lud_filter = unescaped; +#endif + } + + p = q; + if(p && !*p) { + rc = LDAP_INVALID_SYNTAX; + + goto quit; + } + +quit: + free(path); + free(query); + + return rc; +} + +static int _ldap_url_parse(struct Curl_easy *data, + const struct connectdata *conn, + LDAPURLDesc **ludpp) +{ + LDAPURLDesc *ludp = calloc(1, sizeof(*ludp)); + int rc; + + *ludpp = NULL; + if(!ludp) + return LDAP_NO_MEMORY; + + rc = _ldap_url_parse2(data, conn, ludp); + if(rc != LDAP_SUCCESS) { + _ldap_free_urldesc(ludp); + ludp = NULL; + } + *ludpp = ludp; + return (rc); +} + +static void _ldap_free_urldesc(LDAPURLDesc *ludp) +{ + if(!ludp) + return; + +#if defined(USE_WIN32_LDAP) + curlx_unicodefree(ludp->lud_dn); + curlx_unicodefree(ludp->lud_filter); +#else + free(ludp->lud_dn); + free(ludp->lud_filter); +#endif + + if(ludp->lud_attrs) { + size_t i; + for(i = 0; i < ludp->lud_attrs_dups; i++) { +#if defined(USE_WIN32_LDAP) + curlx_unicodefree(ludp->lud_attrs[i]); +#else + free(ludp->lud_attrs[i]); +#endif + } + free(ludp->lud_attrs); + } + + free(ludp); +} +#endif /* !HAVE_LDAP_URL_PARSE */ +#endif /* !CURL_DISABLE_LDAP && !USE_OPENLDAP */ diff --git a/Utilities/cmcurl/lib/libcurl.rc b/Utilities/cmcurl/lib/libcurl.rc new file mode 100644 index 0000000..daa2d62 --- /dev/null +++ b/Utilities/cmcurl/lib/libcurl.rc @@ -0,0 +1,65 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include <winver.h> +#include "../include/curl/curlver.h" + +LANGUAGE 0, 0 + +#define RC_VERSION LIBCURL_VERSION_MAJOR, LIBCURL_VERSION_MINOR, LIBCURL_VERSION_PATCH, 0 + +VS_VERSION_INFO VERSIONINFO + FILEVERSION RC_VERSION + PRODUCTVERSION RC_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#if defined(DEBUGBUILD) || defined(_DEBUG) + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0L + +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "The curl library, https://curl.se/\0" + VALUE "FileDescription", "libcurl Shared Library\0" + VALUE "FileVersion", LIBCURL_VERSION "\0" + VALUE "InternalName", "libcurl\0" + VALUE "OriginalFilename", "libcurl.dll\0" + VALUE "ProductName", "The curl library\0" + VALUE "ProductVersion", LIBCURL_VERSION "\0" + VALUE "LegalCopyright", "Copyright (C) " LIBCURL_COPYRIGHT "\0" + VALUE "License", "https://curl.se/docs/copyright.html\0" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/Utilities/cmcurl/lib/llist.c b/Utilities/cmcurl/lib/llist.c new file mode 100644 index 0000000..5b6b033 --- /dev/null +++ b/Utilities/cmcurl/lib/llist.c @@ -0,0 +1,146 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "llist.h" +#include "curl_memory.h" + +/* this must be the last include file */ +#include "memdebug.h" + +/* + * @unittest: 1300 + */ +void +Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor) +{ + l->size = 0; + l->dtor = dtor; + l->head = NULL; + l->tail = NULL; +} + +/* + * Curl_llist_insert_next() + * + * Inserts a new list element after the given one 'e'. If the given existing + * entry is NULL and the list already has elements, the new one will be + * inserted first in the list. + * + * The 'ne' argument should be a pointer into the object to store. + * + * @unittest: 1300 + */ +void +Curl_llist_insert_next(struct Curl_llist *list, struct Curl_llist_element *e, + const void *p, + struct Curl_llist_element *ne) +{ + ne->ptr = (void *) p; + if(list->size == 0) { + list->head = ne; + list->head->prev = NULL; + list->head->next = NULL; + list->tail = ne; + } + else { + /* if 'e' is NULL here, we insert the new element first in the list */ + ne->next = e?e->next:list->head; + ne->prev = e; + if(!e) { + list->head->prev = ne; + list->head = ne; + } + else if(e->next) { + e->next->prev = ne; + } + else { + list->tail = ne; + } + if(e) + e->next = ne; + } + + ++list->size; +} + +/* + * @unittest: 1300 + */ +void +Curl_llist_remove(struct Curl_llist *list, struct Curl_llist_element *e, + void *user) +{ + void *ptr; + if(!e || list->size == 0) + return; + + if(e == list->head) { + list->head = e->next; + + if(!list->head) + list->tail = NULL; + else + e->next->prev = NULL; + } + else { + if(e->prev) + e->prev->next = e->next; + + if(!e->next) + list->tail = e->prev; + else + e->next->prev = e->prev; + } + + ptr = e->ptr; + + e->ptr = NULL; + e->prev = NULL; + e->next = NULL; + + --list->size; + + /* call the dtor() last for when it actually frees the 'e' memory itself */ + if(list->dtor) + list->dtor(user, ptr); +} + +void +Curl_llist_destroy(struct Curl_llist *list, void *user) +{ + if(list) { + while(list->size > 0) + Curl_llist_remove(list, list->tail, user); + } +} + +size_t +Curl_llist_count(struct Curl_llist *list) +{ + return list->size; +} diff --git a/Utilities/cmcurl/lib/llist.h b/Utilities/cmcurl/lib/llist.h new file mode 100644 index 0000000..320580e --- /dev/null +++ b/Utilities/cmcurl/lib/llist.h @@ -0,0 +1,52 @@ +#ifndef HEADER_CURL_LLIST_H +#define HEADER_CURL_LLIST_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include <stddef.h> + +typedef void (*Curl_llist_dtor)(void *, void *); + +struct Curl_llist_element { + void *ptr; + struct Curl_llist_element *prev; + struct Curl_llist_element *next; +}; + +struct Curl_llist { + struct Curl_llist_element *head; + struct Curl_llist_element *tail; + Curl_llist_dtor dtor; + size_t size; +}; + +void Curl_llist_init(struct Curl_llist *, Curl_llist_dtor); +void Curl_llist_insert_next(struct Curl_llist *, struct Curl_llist_element *, + const void *, struct Curl_llist_element *node); +void Curl_llist_remove(struct Curl_llist *, struct Curl_llist_element *, + void *); +size_t Curl_llist_count(struct Curl_llist *); +void Curl_llist_destroy(struct Curl_llist *, void *); +#endif /* HEADER_CURL_LLIST_H */ diff --git a/Utilities/cmcurl/lib/macos.c b/Utilities/cmcurl/lib/macos.c new file mode 100644 index 0000000..9e8e76e --- /dev/null +++ b/Utilities/cmcurl/lib/macos.c @@ -0,0 +1,55 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef CURL_MACOS_CALL_COPYPROXIES + +#include <curl/curl.h> + +#include "macos.h" + +#include <SystemConfiguration/SCDynamicStoreCopySpecific.h> + +CURLcode Curl_macos_init(void) +{ + { + /* + * The automagic conversion from IPv4 literals to IPv6 literals only + * works if the SCDynamicStoreCopyProxies system function gets called + * first. As Curl currently doesn't support system-wide HTTP proxies, we + * therefore don't use any value this function might return. + * + * This function is only available on macOS and is not needed for + * IPv4-only builds, hence the conditions for defining + * CURL_MACOS_CALL_COPYPROXIES in curl_setup.h. + */ + CFDictionaryRef dict = SCDynamicStoreCopyProxies(NULL); + if(dict) + CFRelease(dict); + } + return CURLE_OK; +} + +#endif diff --git a/Utilities/cmcurl/lib/macos.h b/Utilities/cmcurl/lib/macos.h new file mode 100644 index 0000000..637860e --- /dev/null +++ b/Utilities/cmcurl/lib/macos.h @@ -0,0 +1,39 @@ +#ifndef HEADER_CURL_MACOS_H +#define HEADER_CURL_MACOS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef CURL_MACOS_CALL_COPYPROXIES + +CURLcode Curl_macos_init(void); + +#else + +#define Curl_macos_init() CURLE_OK + +#endif + +#endif /* HEADER_CURL_MACOS_H */ diff --git a/Utilities/cmcurl/lib/md4.c b/Utilities/cmcurl/lib/md4.c new file mode 100644 index 0000000..486e5fa --- /dev/null +++ b/Utilities/cmcurl/lib/md4.c @@ -0,0 +1,526 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_CURL_NTLM_CORE) + +#include <string.h> + +#include "curl_md4.h" +#include "warnless.h" + +#ifdef USE_OPENSSL +#include <openssl/opensslv.h> +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) && !defined(USE_AMISSL) +/* OpenSSL 3.0.0 marks the MD4 functions as deprecated */ +#define OPENSSL_NO_MD4 +#endif +#endif /* USE_OPENSSL */ + +#ifdef USE_WOLFSSL +#include <wolfssl/options.h> +#define VOID_MD4_INIT +#ifdef NO_MD4 +#define WOLFSSL_NO_MD4 +#endif +#endif + +#ifdef USE_MBEDTLS +#include <mbedtls/version.h> +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 +#include <mbedtls/mbedtls_config.h> +#else +#include <mbedtls/config.h> +#endif +#if(MBEDTLS_VERSION_NUMBER >= 0x02070000) + #define HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS +#endif +#endif /* USE_MBEDTLS */ + +#if defined(USE_GNUTLS) +#include <nettle/md4.h> +/* When OpenSSL or wolfSSL is available, we use their MD4 functions. */ +#elif defined(USE_WOLFSSL) && !defined(WOLFSSL_NO_MD4) +#include <wolfssl/openssl/md4.h> +#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4) +#include <openssl/md4.h> +#elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ + (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040) && \ + defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ + (__MAC_OS_X_VERSION_MIN_REQUIRED < 101500)) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + (__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000) && \ + defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ + (__IPHONE_OS_VERSION_MIN_REQUIRED < 130000)) +#define AN_APPLE_OS +#include <CommonCrypto/CommonDigest.h> +#elif defined(USE_WIN32_CRYPTO) +#include <wincrypt.h> +#elif(defined(USE_MBEDTLS) && defined(MBEDTLS_MD4_C)) +#include <mbedtls/md4.h> +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +#if defined(USE_GNUTLS) + +typedef struct md4_ctx MD4_CTX; + +static int MD4_Init(MD4_CTX *ctx) +{ + md4_init(ctx); + return 1; +} + +static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) +{ + md4_update(ctx, size, data); +} + +static void MD4_Final(unsigned char *result, MD4_CTX *ctx) +{ + md4_digest(ctx, MD4_DIGEST_SIZE, result); +} + +#elif defined(USE_WOLFSSL) && !defined(WOLFSSL_NO_MD4) + +#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4) + +#elif defined(AN_APPLE_OS) +typedef CC_MD4_CTX MD4_CTX; + +static int MD4_Init(MD4_CTX *ctx) +{ + return CC_MD4_Init(ctx); +} + +static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) +{ + (void)CC_MD4_Update(ctx, data, (CC_LONG)size); +} + +static void MD4_Final(unsigned char *result, MD4_CTX *ctx) +{ + (void)CC_MD4_Final(result, ctx); +} + +#elif defined(USE_WIN32_CRYPTO) + +struct md4_ctx { + HCRYPTPROV hCryptProv; + HCRYPTHASH hHash; +}; +typedef struct md4_ctx MD4_CTX; + +static int MD4_Init(MD4_CTX *ctx) +{ + ctx->hCryptProv = 0; + ctx->hHash = 0; + + if(!CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + return 0; + + if(!CryptCreateHash(ctx->hCryptProv, CALG_MD4, 0, 0, &ctx->hHash)) { + CryptReleaseContext(ctx->hCryptProv, 0); + ctx->hCryptProv = 0; + return 0; + } + + return 1; +} + +static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) +{ + CryptHashData(ctx->hHash, (BYTE *)data, (unsigned int) size, 0); +} + +static void MD4_Final(unsigned char *result, MD4_CTX *ctx) +{ + unsigned long length = 0; + + CryptGetHashParam(ctx->hHash, HP_HASHVAL, NULL, &length, 0); + if(length == MD4_DIGEST_LENGTH) + CryptGetHashParam(ctx->hHash, HP_HASHVAL, result, &length, 0); + + if(ctx->hHash) + CryptDestroyHash(ctx->hHash); + + if(ctx->hCryptProv) + CryptReleaseContext(ctx->hCryptProv, 0); +} + +#elif(defined(USE_MBEDTLS) && defined(MBEDTLS_MD4_C)) + +struct md4_ctx { + void *data; + unsigned long size; +}; +typedef struct md4_ctx MD4_CTX; + +static int MD4_Init(MD4_CTX *ctx) +{ + ctx->data = NULL; + ctx->size = 0; + return 1; +} + +static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) +{ + if(!ctx->data) { + ctx->data = malloc(size); + if(ctx->data) { + memcpy(ctx->data, data, size); + ctx->size = size; + } + } +} + +static void MD4_Final(unsigned char *result, MD4_CTX *ctx) +{ + if(ctx->data) { +#if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) + mbedtls_md4(ctx->data, ctx->size, result); +#else + (void) mbedtls_md4_ret(ctx->data, ctx->size, result); +#endif + + Curl_safefree(ctx->data); + ctx->size = 0; + } +} + +#else +/* When no other crypto library is available, or the crypto library doesn't + * support MD4, we use this code segment this implementation of it + * + * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. + * MD4 Message-Digest Algorithm (RFC 1320). + * + * Homepage: + https://openwall.info/wiki/people/solar/software/public-domain-source-code/md4 + * + * Author: + * Alexander Peslyak, better known as Solar Designer <solar at openwall.com> + * + * This software was written by Alexander Peslyak in 2001. No copyright is + * claimed, and the software is hereby placed in the public domain. In case + * this attempt to disclaim copyright and place the software in the public + * domain is deemed null and void, then the software is Copyright (c) 2001 + * Alexander Peslyak and it is hereby released to the general public under the + * following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * (This is a heavily cut-down "BSD license".) + * + * This differs from Colin Plumb's older public domain implementation in that + * no exactly 32-bit integer data type is required (any 32-bit or wider + * unsigned integer data type will do), there's no compile-time endianness + * configuration, and the function prototypes match OpenSSL's. No code from + * Colin Plumb's implementation has been reused; this comment merely compares + * the properties of the two independent implementations. + * + * The primary goals of this implementation are portability and ease of use. + * It is meant to be fast, but not as fast as possible. Some known + * optimizations are not included to reduce source code size and avoid + * compile-time configuration. + */ + +/* Any 32-bit or wider unsigned integer data type will do */ +typedef unsigned int MD4_u32plus; + +struct md4_ctx { + MD4_u32plus lo, hi; + MD4_u32plus a, b, c, d; + unsigned char buffer[64]; + MD4_u32plus block[16]; +}; +typedef struct md4_ctx MD4_CTX; + +static int MD4_Init(MD4_CTX *ctx); +static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size); +static void MD4_Final(unsigned char *result, MD4_CTX *ctx); + +/* + * The basic MD4 functions. + * + * F and G are optimized compared to their RFC 1320 definitions, with the + * optimization for F borrowed from Colin Plumb's MD5 implementation. + */ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) (((x) & ((y) | (z))) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* + * The MD4 transformation for all three rounds. + */ +#define STEP(f, a, b, c, d, x, s) \ + (a) += f((b), (c), (d)) + (x); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); + +/* + * SET reads 4 input bytes in little-endian byte order and stores them + * in a properly aligned word in host byte order. + * + * The check for little-endian architectures that tolerate unaligned + * memory accesses is just an optimization. Nothing will break if it + * doesn't work. + */ +#if defined(__i386__) || defined(__x86_64__) || defined(__vax__) +#define SET(n) \ + (*(MD4_u32plus *)(void *)&ptr[(n) * 4]) +#define GET(n) \ + SET(n) +#else +#define SET(n) \ + (ctx->block[(n)] = \ + (MD4_u32plus)ptr[(n) * 4] | \ + ((MD4_u32plus)ptr[(n) * 4 + 1] << 8) | \ + ((MD4_u32plus)ptr[(n) * 4 + 2] << 16) | \ + ((MD4_u32plus)ptr[(n) * 4 + 3] << 24)) +#define GET(n) \ + (ctx->block[(n)]) +#endif + +/* + * This processes one or more 64-byte data blocks, but does NOT update + * the bit counters. There are no alignment requirements. + */ +static const void *body(MD4_CTX *ctx, const void *data, unsigned long size) +{ + const unsigned char *ptr; + MD4_u32plus a, b, c, d; + + ptr = (const unsigned char *)data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + MD4_u32plus saved_a, saved_b, saved_c, saved_d; + + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + +/* Round 1 */ + STEP(F, a, b, c, d, SET(0), 3) + STEP(F, d, a, b, c, SET(1), 7) + STEP(F, c, d, a, b, SET(2), 11) + STEP(F, b, c, d, a, SET(3), 19) + STEP(F, a, b, c, d, SET(4), 3) + STEP(F, d, a, b, c, SET(5), 7) + STEP(F, c, d, a, b, SET(6), 11) + STEP(F, b, c, d, a, SET(7), 19) + STEP(F, a, b, c, d, SET(8), 3) + STEP(F, d, a, b, c, SET(9), 7) + STEP(F, c, d, a, b, SET(10), 11) + STEP(F, b, c, d, a, SET(11), 19) + STEP(F, a, b, c, d, SET(12), 3) + STEP(F, d, a, b, c, SET(13), 7) + STEP(F, c, d, a, b, SET(14), 11) + STEP(F, b, c, d, a, SET(15), 19) + +/* Round 2 */ + STEP(G, a, b, c, d, GET(0) + 0x5a827999, 3) + STEP(G, d, a, b, c, GET(4) + 0x5a827999, 5) + STEP(G, c, d, a, b, GET(8) + 0x5a827999, 9) + STEP(G, b, c, d, a, GET(12) + 0x5a827999, 13) + STEP(G, a, b, c, d, GET(1) + 0x5a827999, 3) + STEP(G, d, a, b, c, GET(5) + 0x5a827999, 5) + STEP(G, c, d, a, b, GET(9) + 0x5a827999, 9) + STEP(G, b, c, d, a, GET(13) + 0x5a827999, 13) + STEP(G, a, b, c, d, GET(2) + 0x5a827999, 3) + STEP(G, d, a, b, c, GET(6) + 0x5a827999, 5) + STEP(G, c, d, a, b, GET(10) + 0x5a827999, 9) + STEP(G, b, c, d, a, GET(14) + 0x5a827999, 13) + STEP(G, a, b, c, d, GET(3) + 0x5a827999, 3) + STEP(G, d, a, b, c, GET(7) + 0x5a827999, 5) + STEP(G, c, d, a, b, GET(11) + 0x5a827999, 9) + STEP(G, b, c, d, a, GET(15) + 0x5a827999, 13) + +/* Round 3 */ + STEP(H, a, b, c, d, GET(0) + 0x6ed9eba1, 3) + STEP(H, d, a, b, c, GET(8) + 0x6ed9eba1, 9) + STEP(H, c, d, a, b, GET(4) + 0x6ed9eba1, 11) + STEP(H, b, c, d, a, GET(12) + 0x6ed9eba1, 15) + STEP(H, a, b, c, d, GET(2) + 0x6ed9eba1, 3) + STEP(H, d, a, b, c, GET(10) + 0x6ed9eba1, 9) + STEP(H, c, d, a, b, GET(6) + 0x6ed9eba1, 11) + STEP(H, b, c, d, a, GET(14) + 0x6ed9eba1, 15) + STEP(H, a, b, c, d, GET(1) + 0x6ed9eba1, 3) + STEP(H, d, a, b, c, GET(9) + 0x6ed9eba1, 9) + STEP(H, c, d, a, b, GET(5) + 0x6ed9eba1, 11) + STEP(H, b, c, d, a, GET(13) + 0x6ed9eba1, 15) + STEP(H, a, b, c, d, GET(3) + 0x6ed9eba1, 3) + STEP(H, d, a, b, c, GET(11) + 0x6ed9eba1, 9) + STEP(H, c, d, a, b, GET(7) + 0x6ed9eba1, 11) + STEP(H, b, c, d, a, GET(15) + 0x6ed9eba1, 15) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while(size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +static int MD4_Init(MD4_CTX *ctx) +{ + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; + return 1; +} + +static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) +{ + MD4_u32plus saved_lo; + unsigned long used; + + saved_lo = ctx->lo; + ctx->lo = (saved_lo + size) & 0x1fffffff; + if(ctx->lo < saved_lo) + ctx->hi++; + ctx->hi += (MD4_u32plus)size >> 29; + + used = saved_lo & 0x3f; + + if(used) { + unsigned long available = 64 - used; + + if(size < available) { + memcpy(&ctx->buffer[used], data, size); + return; + } + + memcpy(&ctx->buffer[used], data, available); + data = (const unsigned char *)data + available; + size -= available; + body(ctx, ctx->buffer, 64); + } + + if(size >= 64) { + data = body(ctx, data, size & ~(unsigned long)0x3f); + size &= 0x3f; + } + + memcpy(ctx->buffer, data, size); +} + +static void MD4_Final(unsigned char *result, MD4_CTX *ctx) +{ + unsigned long used, available; + + used = ctx->lo & 0x3f; + + ctx->buffer[used++] = 0x80; + + available = 64 - used; + + if(available < 8) { + memset(&ctx->buffer[used], 0, available); + body(ctx, ctx->buffer, 64); + used = 0; + available = 64; + } + + memset(&ctx->buffer[used], 0, available - 8); + + ctx->lo <<= 3; + ctx->buffer[56] = curlx_ultouc((ctx->lo)&0xff); + ctx->buffer[57] = curlx_ultouc((ctx->lo >> 8)&0xff); + ctx->buffer[58] = curlx_ultouc((ctx->lo >> 16)&0xff); + ctx->buffer[59] = curlx_ultouc((ctx->lo >> 24)&0xff); + ctx->buffer[60] = curlx_ultouc((ctx->hi)&0xff); + ctx->buffer[61] = curlx_ultouc((ctx->hi >> 8)&0xff); + ctx->buffer[62] = curlx_ultouc((ctx->hi >> 16)&0xff); + ctx->buffer[63] = curlx_ultouc(ctx->hi >> 24); + + body(ctx, ctx->buffer, 64); + + result[0] = curlx_ultouc((ctx->a)&0xff); + result[1] = curlx_ultouc((ctx->a >> 8)&0xff); + result[2] = curlx_ultouc((ctx->a >> 16)&0xff); + result[3] = curlx_ultouc(ctx->a >> 24); + result[4] = curlx_ultouc((ctx->b)&0xff); + result[5] = curlx_ultouc((ctx->b >> 8)&0xff); + result[6] = curlx_ultouc((ctx->b >> 16)&0xff); + result[7] = curlx_ultouc(ctx->b >> 24); + result[8] = curlx_ultouc((ctx->c)&0xff); + result[9] = curlx_ultouc((ctx->c >> 8)&0xff); + result[10] = curlx_ultouc((ctx->c >> 16)&0xff); + result[11] = curlx_ultouc(ctx->c >> 24); + result[12] = curlx_ultouc((ctx->d)&0xff); + result[13] = curlx_ultouc((ctx->d >> 8)&0xff); + result[14] = curlx_ultouc((ctx->d >> 16)&0xff); + result[15] = curlx_ultouc(ctx->d >> 24); + + memset(ctx, 0, sizeof(*ctx)); +} + +#endif /* CRYPTO LIBS */ + +CURLcode Curl_md4it(unsigned char *output, const unsigned char *input, + const size_t len) +{ + MD4_CTX ctx; + +#ifdef VOID_MD4_INIT + MD4_Init(&ctx); +#else + if(!MD4_Init(&ctx)) + return CURLE_FAILED_INIT; +#endif + + MD4_Update(&ctx, input, curlx_uztoui(len)); + MD4_Final(output, &ctx); + return CURLE_OK; +} + +#endif /* USE_CURL_NTLM_CORE */ diff --git a/Utilities/cmcurl/lib/md5.c b/Utilities/cmcurl/lib/md5.c new file mode 100644 index 0000000..01415af --- /dev/null +++ b/Utilities/cmcurl/lib/md5.c @@ -0,0 +1,656 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ + || !defined(CURL_DISABLE_DIGEST_AUTH) + +#include <string.h> +#include <curl/curl.h> + +#include "curl_md5.h" +#include "curl_hmac.h" +#include "warnless.h" + +#ifdef USE_MBEDTLS +#include <mbedtls/version.h> + +#if(MBEDTLS_VERSION_NUMBER >= 0x02070000) && \ + (MBEDTLS_VERSION_NUMBER < 0x03000000) + #define HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS +#endif +#endif /* USE_MBEDTLS */ + +#ifdef USE_OPENSSL + #include <openssl/opensslconf.h> + #if !defined(OPENSSL_NO_MD5) && !defined(OPENSSL_NO_DEPRECATED_3_0) + #define USE_OPENSSL_MD5 + #endif +#endif + +#ifdef USE_WOLFSSL + #include <wolfssl/options.h> + #ifndef NO_MD5 + #define USE_WOLFSSL_MD5 + #endif +#endif + +#if defined(USE_GNUTLS) +#include <nettle/md5.h> +#elif defined(USE_OPENSSL_MD5) +#include <openssl/md5.h> +#elif defined(USE_WOLFSSL_MD5) +#include <wolfssl/openssl/md5.h> +#elif defined(USE_MBEDTLS) +#include <mbedtls/md5.h> +#elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ + (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040) && \ + defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ + (__MAC_OS_X_VERSION_MIN_REQUIRED < 101500)) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + (__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000) && \ + defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ + (__IPHONE_OS_VERSION_MIN_REQUIRED < 130000)) +#define AN_APPLE_OS +#include <CommonCrypto/CommonDigest.h> +#elif defined(USE_WIN32_CRYPTO) +#include <wincrypt.h> +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#if defined(USE_GNUTLS) + +typedef struct md5_ctx my_md5_ctx; + +static CURLcode my_md5_init(my_md5_ctx *ctx) +{ + md5_init(ctx); + return CURLE_OK; +} + +static void my_md5_update(my_md5_ctx *ctx, + const unsigned char *input, + unsigned int inputLen) +{ + md5_update(ctx, inputLen, input); +} + +static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +{ + md5_digest(ctx, 16, digest); +} + +#elif defined(USE_OPENSSL_MD5) || defined(USE_WOLFSSL_MD5) + +typedef MD5_CTX my_md5_ctx; + +static CURLcode my_md5_init(my_md5_ctx *ctx) +{ + if(!MD5_Init(ctx)) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +static void my_md5_update(my_md5_ctx *ctx, + const unsigned char *input, + unsigned int len) +{ + (void)MD5_Update(ctx, input, len); +} + +static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +{ + (void)MD5_Final(digest, ctx); +} + +#elif defined(USE_MBEDTLS) + +typedef mbedtls_md5_context my_md5_ctx; + +static CURLcode my_md5_init(my_md5_ctx *ctx) +{ +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) + if(mbedtls_md5_starts(ctx)) + return CURLE_OUT_OF_MEMORY; +#elif defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) + if(mbedtls_md5_starts_ret(ctx)) + return CURLE_OUT_OF_MEMORY; +#else + (void)mbedtls_md5_starts(ctx); +#endif + return CURLE_OK; +} + +static void my_md5_update(my_md5_ctx *ctx, + const unsigned char *data, + unsigned int length) +{ +#if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) + (void) mbedtls_md5_update(ctx, data, length); +#else + (void) mbedtls_md5_update_ret(ctx, data, length); +#endif +} + +static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +{ +#if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) + (void) mbedtls_md5_finish(ctx, digest); +#else + (void) mbedtls_md5_finish_ret(ctx, digest); +#endif +} + +#elif defined(AN_APPLE_OS) + +/* For Apple operating systems: CommonCrypto has the functions we need. + These functions are available on Tiger and later, as well as iOS 2.0 + and later. If you're building for an older cat, well, sorry. + + Declaring the functions as static like this seems to be a bit more + reliable than defining COMMON_DIGEST_FOR_OPENSSL on older cats. */ +# define my_md5_ctx CC_MD5_CTX + +static CURLcode my_md5_init(my_md5_ctx *ctx) +{ + if(!CC_MD5_Init(ctx)) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +static void my_md5_update(my_md5_ctx *ctx, + const unsigned char *input, + unsigned int inputLen) +{ + CC_MD5_Update(ctx, input, inputLen); +} + +static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +{ + CC_MD5_Final(digest, ctx); +} + +#elif defined(USE_WIN32_CRYPTO) + +struct md5_ctx { + HCRYPTPROV hCryptProv; + HCRYPTHASH hHash; +}; +typedef struct md5_ctx my_md5_ctx; + +static CURLcode my_md5_init(my_md5_ctx *ctx) +{ + if(!CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + return CURLE_OUT_OF_MEMORY; + + if(!CryptCreateHash(ctx->hCryptProv, CALG_MD5, 0, 0, &ctx->hHash)) { + CryptReleaseContext(ctx->hCryptProv, 0); + ctx->hCryptProv = 0; + return CURLE_FAILED_INIT; + } + + return CURLE_OK; +} + +static void my_md5_update(my_md5_ctx *ctx, + const unsigned char *input, + unsigned int inputLen) +{ + CryptHashData(ctx->hHash, (unsigned char *)input, inputLen, 0); +} + +static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +{ + unsigned long length = 0; + CryptGetHashParam(ctx->hHash, HP_HASHVAL, NULL, &length, 0); + if(length == 16) + CryptGetHashParam(ctx->hHash, HP_HASHVAL, digest, &length, 0); + if(ctx->hHash) + CryptDestroyHash(ctx->hHash); + if(ctx->hCryptProv) + CryptReleaseContext(ctx->hCryptProv, 0); +} + +#else + +/* When no other crypto library is available we use this code segment */ + +/* + * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. + * MD5 Message-Digest Algorithm (RFC 1321). + * + * Homepage: + https://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 + * + * Author: + * Alexander Peslyak, better known as Solar Designer <solar at openwall.com> + * + * This software was written by Alexander Peslyak in 2001. No copyright is + * claimed, and the software is hereby placed in the public domain. + * In case this attempt to disclaim copyright and place the software in the + * public domain is deemed null and void, then the software is + * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * (This is a heavily cut-down "BSD license".) + * + * This differs from Colin Plumb's older public domain implementation in that + * no exactly 32-bit integer data type is required (any 32-bit or wider + * unsigned integer data type will do), there's no compile-time endianness + * configuration, and the function prototypes match OpenSSL's. No code from + * Colin Plumb's implementation has been reused; this comment merely compares + * the properties of the two independent implementations. + * + * The primary goals of this implementation are portability and ease of use. + * It is meant to be fast, but not as fast as possible. Some known + * optimizations are not included to reduce source code size and avoid + * compile-time configuration. + */ + +/* Any 32-bit or wider unsigned integer data type will do */ +typedef unsigned int MD5_u32plus; + +struct md5_ctx { + MD5_u32plus lo, hi; + MD5_u32plus a, b, c, d; + unsigned char buffer[64]; + MD5_u32plus block[16]; +}; +typedef struct md5_ctx my_md5_ctx; + +static CURLcode my_md5_init(my_md5_ctx *ctx); +static void my_md5_update(my_md5_ctx *ctx, const void *data, + unsigned long size); +static void my_md5_final(unsigned char *result, my_md5_ctx *ctx); + +/* + * The basic MD5 functions. + * + * F and G are optimized compared to their RFC 1321 definitions for + * architectures that lack an AND-NOT instruction, just like in Colin Plumb's + * implementation. + */ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define H(x, y, z) (((x) ^ (y)) ^ (z)) +#define H2(x, y, z) ((x) ^ ((y) ^ (z))) +#define I(x, y, z) ((y) ^ ((x) | ~(z))) + +/* + * The MD5 transformation for all four rounds. + */ +#define STEP(f, a, b, c, d, x, t, s) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +/* + * SET reads 4 input bytes in little-endian byte order and stores them + * in a properly aligned word in host byte order. + * + * The check for little-endian architectures that tolerate unaligned + * memory accesses is just an optimization. Nothing will break if it + * doesn't work. + */ +#if defined(__i386__) || defined(__x86_64__) || defined(__vax__) +#define SET(n) \ + (*(MD5_u32plus *)(void *)&ptr[(n) * 4]) +#define GET(n) \ + SET(n) +#else +#define SET(n) \ + (ctx->block[(n)] = \ + (MD5_u32plus)ptr[(n) * 4] | \ + ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ + ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ + ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) +#define GET(n) \ + (ctx->block[(n)]) +#endif + +/* + * This processes one or more 64-byte data blocks, but does NOT update + * the bit counters. There are no alignment requirements. + */ +static const void *body(my_md5_ctx *ctx, const void *data, unsigned long size) +{ + const unsigned char *ptr; + MD5_u32plus a, b, c, d; + + ptr = (const unsigned char *)data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + MD5_u32plus saved_a, saved_b, saved_c, saved_d; + + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + +/* Round 1 */ + STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) + STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) + STEP(F, c, d, a, b, SET(2), 0x242070db, 17) + STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) + STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) + STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) + STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) + STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) + STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) + STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) + STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) + STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) + STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) + STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) + STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) + STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) + +/* Round 2 */ + STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) + STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) + STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) + STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) + STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) + STEP(G, d, a, b, c, GET(10), 0x02441453, 9) + STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) + STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) + STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) + STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) + STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) + STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) + STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) + STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) + STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) + STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + +/* Round 3 */ + STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) + STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11) + STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) + STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23) + STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) + STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11) + STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) + STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23) + STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) + STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11) + STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) + STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23) + STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) + STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11) + STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) + STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23) + +/* Round 4 */ + STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) + STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) + STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) + STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) + STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) + STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) + STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) + STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) + STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) + STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) + STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) + STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) + STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) + STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) + STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) + STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while(size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +static CURLcode my_md5_init(my_md5_ctx *ctx) +{ + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; + + return CURLE_OK; +} + +static void my_md5_update(my_md5_ctx *ctx, const void *data, + unsigned long size) +{ + MD5_u32plus saved_lo; + unsigned long used; + + saved_lo = ctx->lo; + ctx->lo = (saved_lo + size) & 0x1fffffff; + if(ctx->lo < saved_lo) + ctx->hi++; + ctx->hi += (MD5_u32plus)size >> 29; + + used = saved_lo & 0x3f; + + if(used) { + unsigned long available = 64 - used; + + if(size < available) { + memcpy(&ctx->buffer[used], data, size); + return; + } + + memcpy(&ctx->buffer[used], data, available); + data = (const unsigned char *)data + available; + size -= available; + body(ctx, ctx->buffer, 64); + } + + if(size >= 64) { + data = body(ctx, data, size & ~(unsigned long)0x3f); + size &= 0x3f; + } + + memcpy(ctx->buffer, data, size); +} + +static void my_md5_final(unsigned char *result, my_md5_ctx *ctx) +{ + unsigned long used, available; + + used = ctx->lo & 0x3f; + + ctx->buffer[used++] = 0x80; + + available = 64 - used; + + if(available < 8) { + memset(&ctx->buffer[used], 0, available); + body(ctx, ctx->buffer, 64); + used = 0; + available = 64; + } + + memset(&ctx->buffer[used], 0, available - 8); + + ctx->lo <<= 3; + ctx->buffer[56] = curlx_ultouc((ctx->lo)&0xff); + ctx->buffer[57] = curlx_ultouc((ctx->lo >> 8)&0xff); + ctx->buffer[58] = curlx_ultouc((ctx->lo >> 16)&0xff); + ctx->buffer[59] = curlx_ultouc(ctx->lo >> 24); + ctx->buffer[60] = curlx_ultouc((ctx->hi)&0xff); + ctx->buffer[61] = curlx_ultouc((ctx->hi >> 8)&0xff); + ctx->buffer[62] = curlx_ultouc((ctx->hi >> 16)&0xff); + ctx->buffer[63] = curlx_ultouc(ctx->hi >> 24); + + body(ctx, ctx->buffer, 64); + + result[0] = curlx_ultouc((ctx->a)&0xff); + result[1] = curlx_ultouc((ctx->a >> 8)&0xff); + result[2] = curlx_ultouc((ctx->a >> 16)&0xff); + result[3] = curlx_ultouc(ctx->a >> 24); + result[4] = curlx_ultouc((ctx->b)&0xff); + result[5] = curlx_ultouc((ctx->b >> 8)&0xff); + result[6] = curlx_ultouc((ctx->b >> 16)&0xff); + result[7] = curlx_ultouc(ctx->b >> 24); + result[8] = curlx_ultouc((ctx->c)&0xff); + result[9] = curlx_ultouc((ctx->c >> 8)&0xff); + result[10] = curlx_ultouc((ctx->c >> 16)&0xff); + result[11] = curlx_ultouc(ctx->c >> 24); + result[12] = curlx_ultouc((ctx->d)&0xff); + result[13] = curlx_ultouc((ctx->d >> 8)&0xff); + result[14] = curlx_ultouc((ctx->d >> 16)&0xff); + result[15] = curlx_ultouc(ctx->d >> 24); + + memset(ctx, 0, sizeof(*ctx)); +} + +#endif /* CRYPTO LIBS */ + +const struct HMAC_params Curl_HMAC_MD5[] = { + { + /* Hash initialization function. */ + CURLX_FUNCTION_CAST(HMAC_hinit_func, my_md5_init), + /* Hash update function. */ + CURLX_FUNCTION_CAST(HMAC_hupdate_func, my_md5_update), + /* Hash computation end function. */ + CURLX_FUNCTION_CAST(HMAC_hfinal_func, my_md5_final), + /* Size of hash context structure. */ + sizeof(my_md5_ctx), + /* Maximum key length. */ + 64, + /* Result size. */ + 16 + } +}; + +const struct MD5_params Curl_DIGEST_MD5[] = { + { + /* Digest initialization function */ + CURLX_FUNCTION_CAST(Curl_MD5_init_func, my_md5_init), + /* Digest update function */ + CURLX_FUNCTION_CAST(Curl_MD5_update_func, my_md5_update), + /* Digest computation end function */ + CURLX_FUNCTION_CAST(Curl_MD5_final_func, my_md5_final), + /* Size of digest context struct */ + sizeof(my_md5_ctx), + /* Result size */ + 16 + } +}; + +/* + * @unittest: 1601 + * Returns CURLE_OK on success. + */ +CURLcode Curl_md5it(unsigned char *outbuffer, const unsigned char *input, + const size_t len) +{ + CURLcode result; + my_md5_ctx ctx; + + result = my_md5_init(&ctx); + if(!result) { + my_md5_update(&ctx, input, curlx_uztoui(len)); + my_md5_final(outbuffer, &ctx); + } + return result; +} + +struct MD5_context *Curl_MD5_init(const struct MD5_params *md5params) +{ + struct MD5_context *ctxt; + + /* Create MD5 context */ + ctxt = malloc(sizeof(*ctxt)); + + if(!ctxt) + return ctxt; + + ctxt->md5_hashctx = malloc(md5params->md5_ctxtsize); + + if(!ctxt->md5_hashctx) { + free(ctxt); + return NULL; + } + + ctxt->md5_hash = md5params; + + if((*md5params->md5_init_func)(ctxt->md5_hashctx)) { + free(ctxt->md5_hashctx); + free(ctxt); + return NULL; + } + + return ctxt; +} + +CURLcode Curl_MD5_update(struct MD5_context *context, + const unsigned char *data, + unsigned int len) +{ + (*context->md5_hash->md5_update_func)(context->md5_hashctx, data, len); + + return CURLE_OK; +} + +CURLcode Curl_MD5_final(struct MD5_context *context, unsigned char *result) +{ + (*context->md5_hash->md5_final_func)(result, context->md5_hashctx); + + free(context->md5_hashctx); + free(context); + + return CURLE_OK; +} + +#endif /* Using NTLM (without SSPI) || Digest */ diff --git a/Utilities/cmcurl/lib/memdebug.c b/Utilities/cmcurl/lib/memdebug.c new file mode 100644 index 0000000..f6ced85 --- /dev/null +++ b/Utilities/cmcurl/lib/memdebug.c @@ -0,0 +1,482 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef CURLDEBUG + +#include <curl/curl.h> + +#include "urldata.h" + +#define MEMDEBUG_NODEFINES /* don't redefine the standard functions */ + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +struct memdebug { + size_t size; + union { + curl_off_t o; + double d; + void *p; + } mem[1]; + /* I'm hoping this is the thing with the strictest alignment + * requirements. That also means we waste some space :-( */ +}; + +/* + * Note that these debug functions are very simple and they are meant to + * remain so. For advanced analysis, record a log file and write perl scripts + * to analyze them! + * + * Don't use these with multithreaded test programs! + */ + +FILE *curl_dbg_logfile = NULL; +static bool registered_cleanup = FALSE; /* atexit registered cleanup */ +static bool memlimit = FALSE; /* enable memory limit */ +static long memsize = 0; /* set number of mallocs allowed */ + +/* LeakSantizier (LSAN) calls _exit() instead of exit() when a leak is detected + on exit so the logfile must be closed explicitly or data could be lost. + Though _exit() does not call atexit handlers such as this, LSAN's call to + _exit() comes after the atexit handlers are called. curl/curl#6620 */ +static void curl_dbg_cleanup(void) +{ + if(curl_dbg_logfile && + curl_dbg_logfile != stderr && + curl_dbg_logfile != stdout) { + fclose(curl_dbg_logfile); + } + curl_dbg_logfile = NULL; +} + +/* this sets the log file name */ +void curl_dbg_memdebug(const char *logname) +{ + if(!curl_dbg_logfile) { + if(logname && *logname) + curl_dbg_logfile = fopen(logname, FOPEN_WRITETEXT); + else + curl_dbg_logfile = stderr; +#ifdef MEMDEBUG_LOG_SYNC + /* Flush the log file after every line so the log isn't lost in a crash */ + if(curl_dbg_logfile) + setbuf(curl_dbg_logfile, (char *)NULL); +#endif + } + if(!registered_cleanup) + registered_cleanup = !atexit(curl_dbg_cleanup); +} + +/* This function sets the number of malloc() calls that should return + successfully! */ +void curl_dbg_memlimit(long limit) +{ + if(!memlimit) { + memlimit = TRUE; + memsize = limit; + } +} + +/* returns TRUE if this isn't allowed! */ +static bool countcheck(const char *func, int line, const char *source) +{ + /* if source is NULL, then the call is made internally and this check + should not be made */ + if(memlimit && source) { + if(!memsize) { + /* log to file */ + curl_dbg_log("LIMIT %s:%d %s reached memlimit\n", + source, line, func); + /* log to stderr also */ + fprintf(stderr, "LIMIT %s:%d %s reached memlimit\n", + source, line, func); + fflush(curl_dbg_logfile); /* because it might crash now */ + errno = ENOMEM; + return TRUE; /* RETURN ERROR! */ + } + else + memsize--; /* countdown */ + + + } + + return FALSE; /* allow this */ +} + +ALLOC_FUNC void *curl_dbg_malloc(size_t wantedsize, + int line, const char *source) +{ + struct memdebug *mem; + size_t size; + + DEBUGASSERT(wantedsize != 0); + + if(countcheck("malloc", line, source)) + return NULL; + + /* alloc at least 64 bytes */ + size = sizeof(struct memdebug) + wantedsize; + + mem = (Curl_cmalloc)(size); + if(mem) { + mem->size = wantedsize; + } + + if(source) + curl_dbg_log("MEM %s:%d malloc(%zu) = %p\n", + source, line, wantedsize, + mem ? (void *)mem->mem : (void *)0); + + return (mem ? mem->mem : NULL); +} + +ALLOC_FUNC void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_size, + int line, const char *source) +{ + struct memdebug *mem; + size_t size, user_size; + + DEBUGASSERT(wanted_elements != 0); + DEBUGASSERT(wanted_size != 0); + + if(countcheck("calloc", line, source)) + return NULL; + + /* alloc at least 64 bytes */ + user_size = wanted_size * wanted_elements; + size = sizeof(struct memdebug) + user_size; + + mem = (Curl_ccalloc)(1, size); + if(mem) + mem->size = user_size; + + if(source) + curl_dbg_log("MEM %s:%d calloc(%zu,%zu) = %p\n", + source, line, wanted_elements, wanted_size, + mem ? (void *)mem->mem : (void *)0); + + return (mem ? mem->mem : NULL); +} + +ALLOC_FUNC char *curl_dbg_strdup(const char *str, + int line, const char *source) +{ + char *mem; + size_t len; + + DEBUGASSERT(str != NULL); + + if(countcheck("strdup", line, source)) + return NULL; + + len = strlen(str) + 1; + + mem = curl_dbg_malloc(len, 0, NULL); /* NULL prevents logging */ + if(mem) + memcpy(mem, str, len); + + if(source) + curl_dbg_log("MEM %s:%d strdup(%p) (%zu) = %p\n", + source, line, (const void *)str, len, (const void *)mem); + + return mem; +} + +#if defined(_WIN32) && defined(UNICODE) +ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, + int line, const char *source) +{ + wchar_t *mem; + size_t wsiz, bsiz; + + DEBUGASSERT(str != NULL); + + if(countcheck("wcsdup", line, source)) + return NULL; + + wsiz = wcslen(str) + 1; + bsiz = wsiz * sizeof(wchar_t); + + mem = curl_dbg_malloc(bsiz, 0, NULL); /* NULL prevents logging */ + if(mem) + memcpy(mem, str, bsiz); + + if(source) + curl_dbg_log("MEM %s:%d wcsdup(%p) (%zu) = %p\n", + source, line, (void *)str, bsiz, (void *)mem); + + return mem; +} +#endif + +/* We provide a realloc() that accepts a NULL as pointer, which then + performs a malloc(). In order to work with ares. */ +void *curl_dbg_realloc(void *ptr, size_t wantedsize, + int line, const char *source) +{ + struct memdebug *mem = NULL; + + size_t size = sizeof(struct memdebug) + wantedsize; + + DEBUGASSERT(wantedsize != 0); + + if(countcheck("realloc", line, source)) + return NULL; + +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:1684) + /* 1684: conversion from pointer to same-sized integral type */ +#endif + + if(ptr) + mem = (void *)((char *)ptr - offsetof(struct memdebug, mem)); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif + + mem = (Curl_crealloc)(mem, size); + if(source) + curl_dbg_log("MEM %s:%d realloc(%p, %zu) = %p\n", + source, line, (void *)ptr, wantedsize, + mem ? (void *)mem->mem : (void *)0); + + if(mem) { + mem->size = wantedsize; + return mem->mem; + } + + return NULL; +} + +void curl_dbg_free(void *ptr, int line, const char *source) +{ + if(ptr) { + struct memdebug *mem; + +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:1684) + /* 1684: conversion from pointer to same-sized integral type */ +#endif + + mem = (void *)((char *)ptr - offsetof(struct memdebug, mem)); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif + + /* free for real */ + (Curl_cfree)(mem); + } + + if(source && ptr) + curl_dbg_log("MEM %s:%d free(%p)\n", source, line, (void *)ptr); +} + +curl_socket_t curl_dbg_socket(int domain, int type, int protocol, + int line, const char *source) +{ + const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? + "FD %s:%d socket() = %d\n" : + (sizeof(curl_socket_t) == sizeof(long)) ? + "FD %s:%d socket() = %ld\n" : + "FD %s:%d socket() = %zd\n"; + + curl_socket_t sockfd; + + if(countcheck("socket", line, source)) + return CURL_SOCKET_BAD; + + sockfd = socket(domain, type, protocol); + + if(source && (sockfd != CURL_SOCKET_BAD)) + curl_dbg_log(fmt, source, line, sockfd); + + return sockfd; +} + +SEND_TYPE_RETV curl_dbg_send(SEND_TYPE_ARG1 sockfd, + SEND_QUAL_ARG2 SEND_TYPE_ARG2 buf, + SEND_TYPE_ARG3 len, SEND_TYPE_ARG4 flags, int line, + const char *source) +{ + SEND_TYPE_RETV rc; + if(countcheck("send", line, source)) + return -1; + rc = send(sockfd, buf, len, flags); + if(source) + curl_dbg_log("SEND %s:%d send(%lu) = %ld\n", + source, line, (unsigned long)len, (long)rc); + return rc; +} + +RECV_TYPE_RETV curl_dbg_recv(RECV_TYPE_ARG1 sockfd, RECV_TYPE_ARG2 buf, + RECV_TYPE_ARG3 len, RECV_TYPE_ARG4 flags, int line, + const char *source) +{ + RECV_TYPE_RETV rc; + if(countcheck("recv", line, source)) + return -1; + rc = recv(sockfd, buf, len, flags); + if(source) + curl_dbg_log("RECV %s:%d recv(%lu) = %ld\n", + source, line, (unsigned long)len, (long)rc); + return rc; +} + +#ifdef HAVE_SOCKETPAIR +int curl_dbg_socketpair(int domain, int type, int protocol, + curl_socket_t socket_vector[2], + int line, const char *source) +{ + const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? + "FD %s:%d socketpair() = %d %d\n" : + (sizeof(curl_socket_t) == sizeof(long)) ? + "FD %s:%d socketpair() = %ld %ld\n" : + "FD %s:%d socketpair() = %zd %zd\n"; + + int res = socketpair(domain, type, protocol, socket_vector); + + if(source && (0 == res)) + curl_dbg_log(fmt, source, line, socket_vector[0], socket_vector[1]); + + return res; +} +#endif + +curl_socket_t curl_dbg_accept(curl_socket_t s, void *saddr, void *saddrlen, + int line, const char *source) +{ + const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? + "FD %s:%d accept() = %d\n" : + (sizeof(curl_socket_t) == sizeof(long)) ? + "FD %s:%d accept() = %ld\n" : + "FD %s:%d accept() = %zd\n"; + + struct sockaddr *addr = (struct sockaddr *)saddr; + curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen; + + curl_socket_t sockfd = accept(s, addr, addrlen); + + if(source && (sockfd != CURL_SOCKET_BAD)) + curl_dbg_log(fmt, source, line, sockfd); + + return sockfd; +} + +/* separate function to allow libcurl to mark a "faked" close */ +void curl_dbg_mark_sclose(curl_socket_t sockfd, int line, const char *source) +{ + const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? + "FD %s:%d sclose(%d)\n": + (sizeof(curl_socket_t) == sizeof(long)) ? + "FD %s:%d sclose(%ld)\n": + "FD %s:%d sclose(%zd)\n"; + + if(source) + curl_dbg_log(fmt, source, line, sockfd); +} + +/* this is our own defined way to close sockets on *ALL* platforms */ +int curl_dbg_sclose(curl_socket_t sockfd, int line, const char *source) +{ + int res = sclose(sockfd); + curl_dbg_mark_sclose(sockfd, line, source); + return res; +} + +ALLOC_FUNC FILE *curl_dbg_fopen(const char *file, const char *mode, + int line, const char *source) +{ + FILE *res = fopen(file, mode); + + if(source) + curl_dbg_log("FILE %s:%d fopen(\"%s\",\"%s\") = %p\n", + source, line, file, mode, (void *)res); + + return res; +} + +ALLOC_FUNC FILE *curl_dbg_fdopen(int filedes, const char *mode, + int line, const char *source) +{ + FILE *res = fdopen(filedes, mode); + if(source) + curl_dbg_log("FILE %s:%d fdopen(\"%d\",\"%s\") = %p\n", + source, line, filedes, mode, (void *)res); + return res; +} + +int curl_dbg_fclose(FILE *file, int line, const char *source) +{ + int res; + + DEBUGASSERT(file != NULL); + + if(source) + curl_dbg_log("FILE %s:%d fclose(%p)\n", + source, line, (void *)file); + + res = fclose(file); + + return res; +} + +#define LOGLINE_BUFSIZE 1024 + +/* this does the writing to the memory tracking log file */ +void curl_dbg_log(const char *format, ...) +{ + char *buf; + int nchars; + va_list ap; + + if(!curl_dbg_logfile) + return; + + buf = (Curl_cmalloc)(LOGLINE_BUFSIZE); + if(!buf) + return; + + va_start(ap, format); + nchars = mvsnprintf(buf, LOGLINE_BUFSIZE, format, ap); + va_end(ap); + + if(nchars > LOGLINE_BUFSIZE - 1) + nchars = LOGLINE_BUFSIZE - 1; + + if(nchars > 0) + fwrite(buf, 1, (size_t)nchars, curl_dbg_logfile); + + (Curl_cfree)(buf); +} + +#endif /* CURLDEBUG */ diff --git a/Utilities/cmcurl/lib/memdebug.h b/Utilities/cmcurl/lib/memdebug.h new file mode 100644 index 0000000..78a0125 --- /dev/null +++ b/Utilities/cmcurl/lib/memdebug.h @@ -0,0 +1,202 @@ +#ifndef HEADER_CURL_MEMDEBUG_H +#define HEADER_CURL_MEMDEBUG_H +#ifdef CURLDEBUG +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * CAUTION: this header is designed to work when included by the app-side + * as well as the library. Do not mix with library internals! + */ + +#include <curl/curl.h> +#include "functypes.h" + +#if defined(__GNUC__) && __GNUC__ >= 3 +# define ALLOC_FUNC __attribute__((malloc)) +# define ALLOC_SIZE(s) __attribute__((alloc_size(s))) +# define ALLOC_SIZE2(n, s) __attribute__((alloc_size(n, s))) +#elif defined(_MSC_VER) +# define ALLOC_FUNC __declspec(restrict) +# define ALLOC_SIZE(s) +# define ALLOC_SIZE2(n, s) +#else +# define ALLOC_FUNC +# define ALLOC_SIZE(s) +# define ALLOC_SIZE2(n, s) +#endif + +#define CURL_MT_LOGFNAME_BUFSIZE 512 + +extern FILE *curl_dbg_logfile; + +/* memory functions */ +CURL_EXTERN ALLOC_FUNC ALLOC_SIZE(1) void *curl_dbg_malloc(size_t size, + int line, + const char *source); +CURL_EXTERN ALLOC_FUNC ALLOC_SIZE2(1, 2) void *curl_dbg_calloc(size_t elements, + size_t 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); +CURL_EXTERN void curl_dbg_free(void *ptr, int line, const char *source); +CURL_EXTERN ALLOC_FUNC char *curl_dbg_strdup(const char *str, int line, + const char *src); +#if defined(_WIN32) && defined(UNICODE) +CURL_EXTERN ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, + int line, + const char *source); +#endif + +CURL_EXTERN void curl_dbg_memdebug(const char *logname); +CURL_EXTERN void curl_dbg_memlimit(long limit); +CURL_EXTERN void curl_dbg_log(const char *format, ...); + +/* file descriptor manipulators */ +CURL_EXTERN curl_socket_t curl_dbg_socket(int domain, int type, int protocol, + int line, const char *source); +CURL_EXTERN void curl_dbg_mark_sclose(curl_socket_t sockfd, + int line, const char *source); +CURL_EXTERN int curl_dbg_sclose(curl_socket_t sockfd, + int line, const char *source); +CURL_EXTERN curl_socket_t curl_dbg_accept(curl_socket_t s, void *a, void *alen, + int line, const char *source); +#ifdef HAVE_SOCKETPAIR +CURL_EXTERN int curl_dbg_socketpair(int domain, int type, int protocol, + curl_socket_t socket_vector[2], + int line, const char *source); +#endif + +/* send/receive sockets */ +CURL_EXTERN SEND_TYPE_RETV curl_dbg_send(SEND_TYPE_ARG1 sockfd, + SEND_QUAL_ARG2 SEND_TYPE_ARG2 buf, + SEND_TYPE_ARG3 len, + SEND_TYPE_ARG4 flags, int line, + const char *source); +CURL_EXTERN RECV_TYPE_RETV curl_dbg_recv(RECV_TYPE_ARG1 sockfd, + RECV_TYPE_ARG2 buf, + RECV_TYPE_ARG3 len, + RECV_TYPE_ARG4 flags, int line, + const char *source); + +/* FILE functions */ +CURL_EXTERN ALLOC_FUNC FILE *curl_dbg_fopen(const char *file, const char *mode, + int line, const char *source); +CURL_EXTERN ALLOC_FUNC FILE *curl_dbg_fdopen(int filedes, const char *mode, + int line, const char *source); + +CURL_EXTERN int curl_dbg_fclose(FILE *file, int line, const char *source); + +#ifndef MEMDEBUG_NODEFINES + +/* Set this symbol on the command-line, recompile all lib-sources */ +#undef strdup +#define strdup(ptr) curl_dbg_strdup(ptr, __LINE__, __FILE__) +#define malloc(size) curl_dbg_malloc(size, __LINE__, __FILE__) +#define calloc(nbelem,size) curl_dbg_calloc(nbelem, size, __LINE__, __FILE__) +#define realloc(ptr,size) curl_dbg_realloc(ptr, size, __LINE__, __FILE__) +#define free(ptr) curl_dbg_free(ptr, __LINE__, __FILE__) +#define send(a,b,c,d) curl_dbg_send(a,b,c,d, __LINE__, __FILE__) +#define recv(a,b,c,d) curl_dbg_recv(a,b,c,d, __LINE__, __FILE__) + +#ifdef _WIN32 +# ifdef UNICODE +# undef wcsdup +# define wcsdup(ptr) curl_dbg_wcsdup(ptr, __LINE__, __FILE__) +# undef _wcsdup +# define _wcsdup(ptr) curl_dbg_wcsdup(ptr, __LINE__, __FILE__) +# undef _tcsdup +# define _tcsdup(ptr) curl_dbg_wcsdup(ptr, __LINE__, __FILE__) +# else +# undef _tcsdup +# define _tcsdup(ptr) curl_dbg_strdup(ptr, __LINE__, __FILE__) +# endif +#endif + +#undef socket +#define socket(domain,type,protocol)\ + curl_dbg_socket(domain, type, protocol, __LINE__, __FILE__) +#undef accept /* for those with accept as a macro */ +#define accept(sock,addr,len)\ + curl_dbg_accept(sock, addr, len, __LINE__, __FILE__) +#ifdef HAVE_SOCKETPAIR +#define socketpair(domain,type,protocol,socket_vector)\ + curl_dbg_socketpair(domain, type, protocol, socket_vector, __LINE__, __FILE__) +#endif + +#ifdef HAVE_GETADDRINFO +#if defined(getaddrinfo) && defined(__osf__) +/* OSF/1 and Tru64 have getaddrinfo as a define already, so we cannot define + our macro as for other platforms. Instead, we redefine the new name they + define getaddrinfo to become! */ +#define ogetaddrinfo(host,serv,hint,res) \ + curl_dbg_getaddrinfo(host, serv, hint, res, __LINE__, __FILE__) +#else +#undef getaddrinfo +#define getaddrinfo(host,serv,hint,res) \ + curl_dbg_getaddrinfo(host, serv, hint, res, __LINE__, __FILE__) +#endif +#endif /* HAVE_GETADDRINFO */ + +#ifdef HAVE_FREEADDRINFO +#undef freeaddrinfo +#define freeaddrinfo(data) \ + curl_dbg_freeaddrinfo(data, __LINE__, __FILE__) +#endif /* HAVE_FREEADDRINFO */ + +/* sclose is probably already defined, redefine it! */ +#undef sclose +#define sclose(sockfd) curl_dbg_sclose(sockfd,__LINE__,__FILE__) + +#define fake_sclose(sockfd) curl_dbg_mark_sclose(sockfd,__LINE__,__FILE__) + +#undef fopen +#define fopen(file,mode) curl_dbg_fopen(file,mode,__LINE__,__FILE__) +#undef fdopen +#define fdopen(file,mode) curl_dbg_fdopen(file,mode,__LINE__,__FILE__) +#define fclose(file) curl_dbg_fclose(file,__LINE__,__FILE__) + +#endif /* MEMDEBUG_NODEFINES */ + +#endif /* CURLDEBUG */ + +/* +** Following section applies even when CURLDEBUG is not defined. +*/ + +#ifndef fake_sclose +#define fake_sclose(x) Curl_nop_stmt +#endif + +/* + * Curl_safefree defined as a macro to allow MemoryTracking feature + * to log free() calls at same location where Curl_safefree is used. + * This macro also assigns NULL to given pointer when free'd. + */ + +#define Curl_safefree(ptr) \ + do { free((ptr)); (ptr) = NULL;} while(0) + +#endif /* HEADER_CURL_MEMDEBUG_H */ diff --git a/Utilities/cmcurl/lib/mime.c b/Utilities/cmcurl/lib/mime.c new file mode 100644 index 0000000..bb66130 --- /dev/null +++ b/Utilities/cmcurl/lib/mime.c @@ -0,0 +1,2025 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "mime.h" +#include "warnless.h" +#include "urldata.h" +#include "sendf.h" + +#if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ + !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_IMAP)) + +#if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME) +#include <libgen.h> +#endif + +#include "rand.h" +#include "slist.h" +#include "strcase.h" +#include "dynbuf.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef _WIN32 +# ifndef R_OK +# define R_OK 4 +# endif +#endif + + +#define READ_ERROR ((size_t) -1) +#define STOP_FILLING ((size_t) -2) + +static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems, + void *instream, bool *hasread); + +/* Encoders. */ +static size_t encoder_nop_read(char *buffer, size_t size, bool ateof, + curl_mimepart *part); +static curl_off_t encoder_nop_size(curl_mimepart *part); +static size_t encoder_7bit_read(char *buffer, size_t size, bool ateof, + curl_mimepart *part); +static size_t encoder_base64_read(char *buffer, size_t size, bool ateof, + curl_mimepart *part); +static curl_off_t encoder_base64_size(curl_mimepart *part); +static size_t encoder_qp_read(char *buffer, size_t size, bool ateof, + curl_mimepart *part); +static curl_off_t encoder_qp_size(curl_mimepart *part); + +static const struct mime_encoder encoders[] = { + {"binary", encoder_nop_read, encoder_nop_size}, + {"8bit", encoder_nop_read, encoder_nop_size}, + {"7bit", encoder_7bit_read, encoder_nop_size}, + {"base64", encoder_base64_read, encoder_base64_size}, + {"quoted-printable", encoder_qp_read, encoder_qp_size}, + {ZERO_NULL, ZERO_NULL, ZERO_NULL} +}; + +/* Base64 encoding table */ +static const char base64enc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* Quoted-printable character class table. + * + * We cannot rely on ctype functions since quoted-printable input data + * is assumed to be ascii-compatible, even on non-ascii platforms. */ +#define QP_OK 1 /* Can be represented by itself. */ +#define QP_SP 2 /* Space or tab. */ +#define QP_CR 3 /* Carriage return. */ +#define QP_LF 4 /* Line-feed. */ +static const unsigned char qp_class[] = { + 0, 0, 0, 0, 0, 0, 0, 0, /* 00 - 07 */ + 0, QP_SP, QP_LF, 0, 0, QP_CR, 0, 0, /* 08 - 0F */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 10 - 17 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 18 - 1F */ + QP_SP, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, /* 20 - 27 */ + QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, /* 28 - 2F */ + QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, /* 30 - 37 */ + QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, 0 , QP_OK, QP_OK, /* 38 - 3F */ + QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, /* 40 - 47 */ + QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, /* 48 - 4F */ + QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, /* 50 - 57 */ + QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, /* 58 - 5F */ + QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, /* 60 - 67 */ + QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, /* 68 - 6F */ + QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, /* 70 - 77 */ + QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, QP_OK, 0, /* 78 - 7F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 - 8F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 - 9F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A0 - AF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* B0 - BF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* C0 - CF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* D0 - DF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E0 - EF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* F0 - FF */ +}; + + +/* Binary --> hexadecimal ASCII table. */ +static const char aschex[] = + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x41\x42\x43\x44\x45\x46"; + + + +#ifndef __VMS +#define filesize(name, stat_data) (stat_data.st_size) +#define fopen_read fopen + +#else + +#include <fabdef.h> +/* + * get_vms_file_size does what it takes to get the real size of the file + * + * For fixed files, find out the size of the EOF block and adjust. + * + * For all others, have to read the entire file in, discarding the contents. + * Most posted text files will be small, and binary files like zlib archives + * and CD/DVD images should be either a STREAM_LF format or a fixed format. + * + */ +curl_off_t VmsRealFileSize(const char *name, + const struct_stat *stat_buf) +{ + char buffer[8192]; + curl_off_t count; + int ret_stat; + FILE * file; + + file = fopen(name, FOPEN_READTEXT); /* VMS */ + if(!file) + return 0; + + count = 0; + ret_stat = 1; + while(ret_stat > 0) { + ret_stat = fread(buffer, 1, sizeof(buffer), file); + if(ret_stat) + count += ret_stat; + } + fclose(file); + + return count; +} + +/* + * + * VmsSpecialSize checks to see if the stat st_size can be trusted and + * if not to call a routine to get the correct size. + * + */ +static curl_off_t VmsSpecialSize(const char *name, + const struct_stat *stat_buf) +{ + switch(stat_buf->st_fab_rfm) { + case FAB$C_VAR: + case FAB$C_VFC: + return VmsRealFileSize(name, stat_buf); + break; + default: + return stat_buf->st_size; + } +} + +#define filesize(name, stat_data) VmsSpecialSize(name, &stat_data) + +/* + * vmsfopenread + * + * For upload to work as expected on VMS, different optional + * parameters must be added to the fopen command based on + * record format of the file. + * + */ +static FILE * vmsfopenread(const char *file, const char *mode) +{ + struct_stat statbuf; + int result; + + result = stat(file, &statbuf); + + switch(statbuf.st_fab_rfm) { + case FAB$C_VAR: + case FAB$C_VFC: + case FAB$C_STMCR: + return fopen(file, FOPEN_READTEXT); /* VMS */ + break; + default: + return fopen(file, FOPEN_READTEXT, "rfm=stmlf", "ctx=stm"); + } +} + +#define fopen_read vmsfopenread +#endif + + +#ifndef HAVE_BASENAME +/* + (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. + + 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 ".". + + 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. + +*/ +static char *Curl_basename(char *path) +{ + /* Ignore all the details above for now and make a quick and simple + implementation here */ + char *s1; + char *s2; + + s1 = strrchr(path, '/'); + s2 = strrchr(path, '\\'); + + if(s1 && s2) { + path = (s1 > s2? s1 : s2) + 1; + } + else if(s1) + path = s1 + 1; + else if(s2) + path = s2 + 1; + + return path; +} + +#define basename(x) Curl_basename((x)) +#endif + + +/* Set readback state. */ +static void mimesetstate(struct mime_state *state, + enum mimestate tok, void *ptr) +{ + state->state = tok; + state->ptr = ptr; + state->offset = 0; +} + + +/* Escape header string into allocated memory. */ +static char *escape_string(struct Curl_easy *data, + const char *src, enum mimestrategy strategy) +{ + CURLcode result; + struct dynbuf db; + const char * const *table; + const char * const *p; + /* replace first character by rest of string. */ + static const char * const mimetable[] = { + "\\\\\\", + "\"\\\"", + NULL + }; + /* WHATWG HTML living standard 4.10.21.8 2 specifies: + For field names and filenames for file fields, the result of the + encoding in the previous bullet point must be escaped by replacing + any 0x0A (LF) bytes with the byte sequence `%0A`, 0x0D (CR) with `%0D` + and 0x22 (") with `%22`. + The user agent must not perform any other escapes. */ + static const char * const formtable[] = { + "\"%22", + "\r%0D", + "\n%0A", + NULL + }; + + table = formtable; + /* data can be NULL when this function is called indirectly from + curl_formget(). */ + if(strategy == MIMESTRATEGY_MAIL || (data && (data->set.mime_formescape))) + table = mimetable; + + Curl_dyn_init(&db, CURL_MAX_INPUT_LENGTH); + + for(result = Curl_dyn_addn(&db, STRCONST("")); !result && *src; src++) { + for(p = table; *p && **p != *src; p++) + ; + + if(*p) + result = Curl_dyn_add(&db, *p + 1); + else + result = Curl_dyn_addn(&db, src, 1); + } + + return Curl_dyn_ptr(&db); +} + +/* Check if header matches. */ +static char *match_header(struct curl_slist *hdr, const char *lbl, size_t len) +{ + char *value = NULL; + + if(strncasecompare(hdr->data, lbl, len) && hdr->data[len] == ':') + for(value = hdr->data + len + 1; *value == ' '; value++) + ; + return value; +} + +/* Get a header from an slist. */ +static char *search_header(struct curl_slist *hdrlist, + const char *hdr, size_t len) +{ + char *value = NULL; + + for(; !value && hdrlist; hdrlist = hdrlist->next) + value = match_header(hdrlist, hdr, len); + + return value; +} + +static char *strippath(const char *fullfile) +{ + char *filename; + char *base; + filename = strdup(fullfile); /* duplicate since basename() may ruin the + buffer it works on */ + if(!filename) + return NULL; + base = strdup(basename(filename)); + + free(filename); /* free temporary buffer */ + + return base; /* returns an allocated string or NULL ! */ +} + +/* Initialize data encoder state. */ +static void cleanup_encoder_state(struct mime_encoder_state *p) +{ + p->pos = 0; + p->bufbeg = 0; + p->bufend = 0; +} + + +/* Dummy encoder. This is used for 8bit and binary content encodings. */ +static size_t encoder_nop_read(char *buffer, size_t size, bool ateof, + struct curl_mimepart *part) +{ + struct mime_encoder_state *st = &part->encstate; + size_t insize = st->bufend - st->bufbeg; + + (void) ateof; + + if(!size) + return STOP_FILLING; + + if(size > insize) + size = insize; + + if(size) + memcpy(buffer, st->buf + st->bufbeg, size); + + st->bufbeg += size; + return size; +} + +static curl_off_t encoder_nop_size(curl_mimepart *part) +{ + return part->datasize; +} + + +/* 7bit encoder: the encoder is just a data validity check. */ +static size_t encoder_7bit_read(char *buffer, size_t size, bool ateof, + curl_mimepart *part) +{ + struct mime_encoder_state *st = &part->encstate; + size_t cursize = st->bufend - st->bufbeg; + + (void) ateof; + + if(!size) + return STOP_FILLING; + + if(size > cursize) + size = cursize; + + for(cursize = 0; cursize < size; cursize++) { + *buffer = st->buf[st->bufbeg]; + if(*buffer++ & 0x80) + return cursize? cursize: READ_ERROR; + st->bufbeg++; + } + + return cursize; +} + + +/* Base64 content encoder. */ +static size_t encoder_base64_read(char *buffer, size_t size, bool ateof, + curl_mimepart *part) +{ + struct mime_encoder_state *st = &part->encstate; + size_t cursize = 0; + int i; + char *ptr = buffer; + + while(st->bufbeg < st->bufend) { + /* Line full ? */ + if(st->pos > MAX_ENCODED_LINE_LENGTH - 4) { + /* Yes, we need 2 characters for CRLF. */ + if(size < 2) { + if(!cursize) + return STOP_FILLING; + break; + } + *ptr++ = '\r'; + *ptr++ = '\n'; + st->pos = 0; + cursize += 2; + size -= 2; + } + + /* Be sure there is enough space and input data for a base64 group. */ + if(size < 4) { + if(!cursize) + return STOP_FILLING; + break; + } + if(st->bufend - st->bufbeg < 3) + break; + + /* Encode three bytes as four characters. */ + i = st->buf[st->bufbeg++] & 0xFF; + i = (i << 8) | (st->buf[st->bufbeg++] & 0xFF); + i = (i << 8) | (st->buf[st->bufbeg++] & 0xFF); + *ptr++ = base64enc[(i >> 18) & 0x3F]; + *ptr++ = base64enc[(i >> 12) & 0x3F]; + *ptr++ = base64enc[(i >> 6) & 0x3F]; + *ptr++ = base64enc[i & 0x3F]; + cursize += 4; + st->pos += 4; + size -= 4; + } + + /* If at eof, we have to flush the buffered data. */ + if(ateof) { + if(size < 4) { + if(!cursize) + return STOP_FILLING; + } + else { + /* Buffered data size can only be 0, 1 or 2. */ + ptr[2] = ptr[3] = '='; + i = 0; + + /* If there is buffered data */ + if(st->bufend != st->bufbeg) { + + if(st->bufend - st->bufbeg == 2) + i = (st->buf[st->bufbeg + 1] & 0xFF) << 8; + + i |= (st->buf[st->bufbeg] & 0xFF) << 16; + ptr[0] = base64enc[(i >> 18) & 0x3F]; + ptr[1] = base64enc[(i >> 12) & 0x3F]; + if(++st->bufbeg != st->bufend) { + ptr[2] = base64enc[(i >> 6) & 0x3F]; + st->bufbeg++; + } + cursize += 4; + st->pos += 4; + } + } + } + + return cursize; +} + +static curl_off_t encoder_base64_size(curl_mimepart *part) +{ + curl_off_t size = part->datasize; + + if(size <= 0) + return size; /* Unknown size or no data. */ + + /* Compute base64 character count. */ + size = 4 * (1 + (size - 1) / 3); + + /* Effective character count must include CRLFs. */ + return size + 2 * ((size - 1) / MAX_ENCODED_LINE_LENGTH); +} + + +/* Quoted-printable lookahead. + * + * Check if a CRLF or end of data is in input buffer at current position + n. + * Return -1 if more data needed, 1 if CRLF or end of data, else 0. + */ +static int qp_lookahead_eol(struct mime_encoder_state *st, int ateof, size_t n) +{ + n += st->bufbeg; + if(n >= st->bufend && ateof) + return 1; + if(n + 2 > st->bufend) + return ateof? 0: -1; + if(qp_class[st->buf[n] & 0xFF] == QP_CR && + qp_class[st->buf[n + 1] & 0xFF] == QP_LF) + return 1; + return 0; +} + +/* Quoted-printable encoder. */ +static size_t encoder_qp_read(char *buffer, size_t size, bool ateof, + curl_mimepart *part) +{ + struct mime_encoder_state *st = &part->encstate; + char *ptr = buffer; + size_t cursize = 0; + int softlinebreak; + char buf[4]; + + /* On all platforms, input is supposed to be ASCII compatible: for this + reason, we use hexadecimal ASCII codes in this function rather than + character constants that can be interpreted as non-ascii on some + platforms. Preserve ASCII encoding on output too. */ + while(st->bufbeg < st->bufend) { + size_t len = 1; + size_t consumed = 1; + int i = st->buf[st->bufbeg]; + buf[0] = (char) i; + buf[1] = aschex[(i >> 4) & 0xF]; + buf[2] = aschex[i & 0xF]; + + switch(qp_class[st->buf[st->bufbeg] & 0xFF]) { + case QP_OK: /* Not a special character. */ + break; + case QP_SP: /* Space or tab. */ + /* Spacing must be escaped if followed by CRLF. */ + switch(qp_lookahead_eol(st, ateof, 1)) { + case -1: /* More input data needed. */ + return cursize; + case 0: /* No encoding needed. */ + break; + default: /* CRLF after space or tab. */ + buf[0] = '\x3D'; /* '=' */ + len = 3; + break; + } + break; + case QP_CR: /* Carriage return. */ + /* If followed by a line-feed, output the CRLF pair. + Else escape it. */ + switch(qp_lookahead_eol(st, ateof, 0)) { + case -1: /* Need more data. */ + return cursize; + case 1: /* CRLF found. */ + buf[len++] = '\x0A'; /* Append '\n'. */ + consumed = 2; + break; + default: /* Not followed by LF: escape. */ + buf[0] = '\x3D'; /* '=' */ + len = 3; + break; + } + break; + default: /* Character must be escaped. */ + buf[0] = '\x3D'; /* '=' */ + len = 3; + break; + } + + /* Be sure the encoded character fits within maximum line length. */ + if(buf[len - 1] != '\x0A') { /* '\n' */ + softlinebreak = st->pos + len > MAX_ENCODED_LINE_LENGTH; + if(!softlinebreak && st->pos + len == MAX_ENCODED_LINE_LENGTH) { + /* We may use the current line only if end of data or followed by + a CRLF. */ + switch(qp_lookahead_eol(st, ateof, consumed)) { + case -1: /* Need more data. */ + return cursize; + case 0: /* Not followed by a CRLF. */ + softlinebreak = 1; + break; + } + } + if(softlinebreak) { + strcpy(buf, "\x3D\x0D\x0A"); /* "=\r\n" */ + len = 3; + consumed = 0; + } + } + + /* If the output buffer would overflow, do not store. */ + if(len > size) { + if(!cursize) + return STOP_FILLING; + break; + } + + /* Append to output buffer. */ + memcpy(ptr, buf, len); + cursize += len; + ptr += len; + size -= len; + st->pos += len; + if(buf[len - 1] == '\x0A') /* '\n' */ + st->pos = 0; + st->bufbeg += consumed; + } + + return cursize; +} + +static curl_off_t encoder_qp_size(curl_mimepart *part) +{ + /* Determining the size can only be done by reading the data: unless the + data size is 0, we return it as unknown (-1). */ + return part->datasize? -1: 0; +} + + +/* In-memory data callbacks. */ +/* Argument is a pointer to the mime part. */ +static size_t mime_mem_read(char *buffer, size_t size, size_t nitems, + void *instream) +{ + curl_mimepart *part = (curl_mimepart *) instream; + size_t sz = curlx_sotouz(part->datasize - part->state.offset); + (void) size; /* Always 1.*/ + + if(!nitems) + return STOP_FILLING; + + if(sz > nitems) + sz = nitems; + + if(sz) + memcpy(buffer, part->data + curlx_sotouz(part->state.offset), sz); + + return sz; +} + +static int mime_mem_seek(void *instream, curl_off_t offset, int whence) +{ + curl_mimepart *part = (curl_mimepart *) instream; + + switch(whence) { + case SEEK_CUR: + offset += part->state.offset; + break; + case SEEK_END: + offset += part->datasize; + break; + } + + if(offset < 0 || offset > part->datasize) + return CURL_SEEKFUNC_FAIL; + + part->state.offset = offset; + return CURL_SEEKFUNC_OK; +} + +static void mime_mem_free(void *ptr) +{ + Curl_safefree(((curl_mimepart *) ptr)->data); +} + + +/* Named file callbacks. */ +/* Argument is a pointer to the mime part. */ +static int mime_open_file(curl_mimepart *part) +{ + /* Open a MIMEKIND_FILE part. */ + + if(part->fp) + return 0; + part->fp = fopen_read(part->data, "rb"); + return part->fp? 0: -1; +} + +static size_t mime_file_read(char *buffer, size_t size, size_t nitems, + void *instream) +{ + curl_mimepart *part = (curl_mimepart *) instream; + + if(!nitems) + return STOP_FILLING; + + if(mime_open_file(part)) + return READ_ERROR; + + return fread(buffer, size, nitems, part->fp); +} + +static int mime_file_seek(void *instream, curl_off_t offset, int whence) +{ + curl_mimepart *part = (curl_mimepart *) instream; + + if(whence == SEEK_SET && !offset && !part->fp) + return CURL_SEEKFUNC_OK; /* Not open: implicitly already at BOF. */ + + if(mime_open_file(part)) + return CURL_SEEKFUNC_FAIL; + + return fseek(part->fp, (long) offset, whence)? + CURL_SEEKFUNC_CANTSEEK: CURL_SEEKFUNC_OK; +} + +static void mime_file_free(void *ptr) +{ + curl_mimepart *part = (curl_mimepart *) ptr; + + if(part->fp) { + fclose(part->fp); + part->fp = NULL; + } + Curl_safefree(part->data); +} + + +/* Subparts callbacks. */ +/* Argument is a pointer to the mime structure. */ + +/* Readback a byte string segment. */ +static size_t readback_bytes(struct mime_state *state, + char *buffer, size_t bufsize, + const char *bytes, size_t numbytes, + const char *trail, size_t traillen) +{ + size_t sz; + size_t offset = curlx_sotouz(state->offset); + + if(numbytes > offset) { + sz = numbytes - offset; + bytes += offset; + } + else { + sz = offset - numbytes; + if(sz >= traillen) + return 0; + bytes = trail + sz; + sz = traillen - sz; + } + + if(sz > bufsize) + sz = bufsize; + + memcpy(buffer, bytes, sz); + state->offset += sz; + return sz; +} + +/* Read a non-encoded part content. */ +static size_t read_part_content(curl_mimepart *part, + char *buffer, size_t bufsize, bool *hasread) +{ + size_t sz = 0; + + switch(part->lastreadstatus) { + case 0: + case CURL_READFUNC_ABORT: + case CURL_READFUNC_PAUSE: + case READ_ERROR: + return part->lastreadstatus; + default: + break; + } + + /* If we can determine we are at end of part data, spare a read. */ + if(part->datasize != (curl_off_t) -1 && + part->state.offset >= part->datasize) { + /* sz is already zero. */ + } + else { + switch(part->kind) { + case MIMEKIND_MULTIPART: + /* + * Cannot be processed as other kinds since read function requires + * an additional parameter and is highly recursive. + */ + sz = mime_subparts_read(buffer, 1, bufsize, part->arg, hasread); + break; + case MIMEKIND_FILE: + if(part->fp && feof(part->fp)) + break; /* At EOF. */ + /* FALLTHROUGH */ + default: + if(part->readfunc) { + if(!(part->flags & MIME_FAST_READ)) { + if(*hasread) + return STOP_FILLING; + *hasread = TRUE; + } + sz = part->readfunc(buffer, 1, bufsize, part->arg); + } + break; + } + } + + switch(sz) { + case STOP_FILLING: + break; + case 0: + case CURL_READFUNC_ABORT: + case CURL_READFUNC_PAUSE: + case READ_ERROR: + part->lastreadstatus = sz; + break; + default: + part->state.offset += sz; + part->lastreadstatus = sz; + break; + } + + return sz; +} + +/* Read and encode part content. */ +static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, + size_t bufsize, bool *hasread) +{ + struct mime_encoder_state *st = &part->encstate; + size_t cursize = 0; + size_t sz; + bool ateof = FALSE; + + for(;;) { + if(st->bufbeg < st->bufend || ateof) { + /* Encode buffered data. */ + sz = part->encoder->encodefunc(buffer, bufsize, ateof, part); + switch(sz) { + case 0: + if(ateof) + return cursize; + break; + case READ_ERROR: + case STOP_FILLING: + return cursize? cursize: sz; + default: + cursize += sz; + buffer += sz; + bufsize -= sz; + continue; + } + } + + /* We need more data in input buffer. */ + if(st->bufbeg) { + size_t len = st->bufend - st->bufbeg; + + if(len) + memmove(st->buf, st->buf + st->bufbeg, len); + st->bufbeg = 0; + st->bufend = len; + } + if(st->bufend >= sizeof(st->buf)) + return cursize? cursize: READ_ERROR; /* Buffer full. */ + sz = read_part_content(part, st->buf + st->bufend, + sizeof(st->buf) - st->bufend, hasread); + switch(sz) { + case 0: + ateof = TRUE; + break; + case CURL_READFUNC_ABORT: + case CURL_READFUNC_PAUSE: + case READ_ERROR: + case STOP_FILLING: + return cursize? cursize: sz; + default: + st->bufend += sz; + break; + } + } + + /* NOTREACHED */ +} + +/* Readback a mime part. */ +static size_t readback_part(curl_mimepart *part, + char *buffer, size_t bufsize, bool *hasread) +{ + size_t cursize = 0; + + /* Readback from part. */ + + while(bufsize) { + size_t sz = 0; + struct curl_slist *hdr = (struct curl_slist *) part->state.ptr; + switch(part->state.state) { + case MIMESTATE_BEGIN: + mimesetstate(&part->state, + (part->flags & MIME_BODY_ONLY)? + MIMESTATE_BODY: MIMESTATE_CURLHEADERS, + part->curlheaders); + break; + case MIMESTATE_USERHEADERS: + if(!hdr) { + mimesetstate(&part->state, MIMESTATE_EOH, NULL); + break; + } + if(match_header(hdr, "Content-Type", 12)) { + mimesetstate(&part->state, MIMESTATE_USERHEADERS, hdr->next); + break; + } + /* FALLTHROUGH */ + case MIMESTATE_CURLHEADERS: + if(!hdr) + mimesetstate(&part->state, MIMESTATE_USERHEADERS, part->userheaders); + else { + sz = readback_bytes(&part->state, buffer, bufsize, + hdr->data, strlen(hdr->data), STRCONST("\r\n")); + if(!sz) + mimesetstate(&part->state, part->state.state, hdr->next); + } + break; + case MIMESTATE_EOH: + sz = readback_bytes(&part->state, buffer, bufsize, STRCONST("\r\n"), + STRCONST("")); + if(!sz) + mimesetstate(&part->state, MIMESTATE_BODY, NULL); + break; + case MIMESTATE_BODY: + cleanup_encoder_state(&part->encstate); + mimesetstate(&part->state, MIMESTATE_CONTENT, NULL); + break; + case MIMESTATE_CONTENT: + if(part->encoder) + sz = read_encoded_part_content(part, buffer, bufsize, hasread); + else + sz = read_part_content(part, buffer, bufsize, hasread); + switch(sz) { + case 0: + mimesetstate(&part->state, MIMESTATE_END, NULL); + /* Try sparing open file descriptors. */ + if(part->kind == MIMEKIND_FILE && part->fp) { + fclose(part->fp); + part->fp = NULL; + } + /* FALLTHROUGH */ + case CURL_READFUNC_ABORT: + case CURL_READFUNC_PAUSE: + case READ_ERROR: + case STOP_FILLING: + return cursize? cursize: sz; + } + break; + case MIMESTATE_END: + return cursize; + default: + break; /* Other values not in part state. */ + } + + /* Bump buffer and counters according to read size. */ + cursize += sz; + buffer += sz; + bufsize -= sz; + } + + return cursize; +} + +/* Readback from mime. Warning: not a read callback function. */ +static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems, + void *instream, bool *hasread) +{ + curl_mime *mime = (curl_mime *) instream; + size_t cursize = 0; + (void) size; /* Always 1. */ + + while(nitems) { + size_t sz = 0; + curl_mimepart *part = mime->state.ptr; + switch(mime->state.state) { + case MIMESTATE_BEGIN: + case MIMESTATE_BODY: + mimesetstate(&mime->state, MIMESTATE_BOUNDARY1, mime->firstpart); + /* The first boundary always follows the header termination empty line, + so is always preceded by a CRLF. We can then spare 2 characters + by skipping the leading CRLF in boundary. */ + mime->state.offset += 2; + break; + case MIMESTATE_BOUNDARY1: + sz = readback_bytes(&mime->state, buffer, nitems, STRCONST("\r\n--"), + STRCONST("")); + if(!sz) + mimesetstate(&mime->state, MIMESTATE_BOUNDARY2, part); + break; + case MIMESTATE_BOUNDARY2: + if(part) + sz = readback_bytes(&mime->state, buffer, nitems, mime->boundary, + MIME_BOUNDARY_LEN, STRCONST("\r\n")); + else + sz = readback_bytes(&mime->state, buffer, nitems, mime->boundary, + MIME_BOUNDARY_LEN, STRCONST("--\r\n")); + if(!sz) { + mimesetstate(&mime->state, MIMESTATE_CONTENT, part); + } + break; + case MIMESTATE_CONTENT: + if(!part) { + mimesetstate(&mime->state, MIMESTATE_END, NULL); + break; + } + sz = readback_part(part, buffer, nitems, hasread); + switch(sz) { + case CURL_READFUNC_ABORT: + case CURL_READFUNC_PAUSE: + case READ_ERROR: + case STOP_FILLING: + return cursize? cursize: sz; + case 0: + mimesetstate(&mime->state, MIMESTATE_BOUNDARY1, part->nextpart); + break; + } + break; + case MIMESTATE_END: + return cursize; + default: + break; /* other values not used in mime state. */ + } + + /* Bump buffer and counters according to read size. */ + cursize += sz; + buffer += sz; + nitems -= sz; + } + + return cursize; +} + +static int mime_part_rewind(curl_mimepart *part) +{ + int res = CURL_SEEKFUNC_OK; + enum mimestate targetstate = MIMESTATE_BEGIN; + + if(part->flags & MIME_BODY_ONLY) + targetstate = MIMESTATE_BODY; + cleanup_encoder_state(&part->encstate); + if(part->state.state > targetstate) { + res = CURL_SEEKFUNC_CANTSEEK; + if(part->seekfunc) { + res = part->seekfunc(part->arg, (curl_off_t) 0, SEEK_SET); + switch(res) { + case CURL_SEEKFUNC_OK: + case CURL_SEEKFUNC_FAIL: + case CURL_SEEKFUNC_CANTSEEK: + break; + case -1: /* For fseek() error. */ + res = CURL_SEEKFUNC_CANTSEEK; + break; + default: + res = CURL_SEEKFUNC_FAIL; + break; + } + } + } + + if(res == CURL_SEEKFUNC_OK) + mimesetstate(&part->state, targetstate, NULL); + + part->lastreadstatus = 1; /* Successful read status. */ + return res; +} + +static int mime_subparts_seek(void *instream, curl_off_t offset, int whence) +{ + curl_mime *mime = (curl_mime *) instream; + curl_mimepart *part; + int result = CURL_SEEKFUNC_OK; + + if(whence != SEEK_SET || offset) + return CURL_SEEKFUNC_CANTSEEK; /* Only support full rewind. */ + + if(mime->state.state == MIMESTATE_BEGIN) + return CURL_SEEKFUNC_OK; /* Already rewound. */ + + for(part = mime->firstpart; part; part = part->nextpart) { + int res = mime_part_rewind(part); + if(res != CURL_SEEKFUNC_OK) + result = res; + } + + if(result == CURL_SEEKFUNC_OK) + mimesetstate(&mime->state, MIMESTATE_BEGIN, NULL); + + return result; +} + +/* Release part content. */ +static void cleanup_part_content(curl_mimepart *part) +{ + if(part->freefunc) + part->freefunc(part->arg); + + part->readfunc = NULL; + part->seekfunc = NULL; + part->freefunc = NULL; + part->arg = (void *) part; /* Defaults to part itself. */ + part->data = NULL; + part->fp = NULL; + part->datasize = (curl_off_t) 0; /* No size yet. */ + cleanup_encoder_state(&part->encstate); + part->kind = MIMEKIND_NONE; + part->flags &= ~MIME_FAST_READ; + part->lastreadstatus = 1; /* Successful read status. */ + part->state.state = MIMESTATE_BEGIN; +} + +static void mime_subparts_free(void *ptr) +{ + curl_mime *mime = (curl_mime *) ptr; + + if(mime && mime->parent) { + mime->parent->freefunc = NULL; /* Be sure we won't be called again. */ + cleanup_part_content(mime->parent); /* Avoid dangling pointer in part. */ + } + curl_mime_free(mime); +} + +/* Do not free subparts: unbind them. This is used for the top level only. */ +static void mime_subparts_unbind(void *ptr) +{ + curl_mime *mime = (curl_mime *) ptr; + + if(mime && mime->parent) { + mime->parent->freefunc = NULL; /* Be sure we won't be called again. */ + cleanup_part_content(mime->parent); /* Avoid dangling pointer in part. */ + mime->parent = NULL; + } +} + + +void Curl_mime_cleanpart(curl_mimepart *part) +{ + if(part) { + cleanup_part_content(part); + curl_slist_free_all(part->curlheaders); + if(part->flags & MIME_USERHEADERS_OWNER) + curl_slist_free_all(part->userheaders); + Curl_safefree(part->mimetype); + Curl_safefree(part->name); + Curl_safefree(part->filename); + Curl_mime_initpart(part); + } +} + +/* Recursively delete a mime handle and its parts. */ +void curl_mime_free(curl_mime *mime) +{ + curl_mimepart *part; + + if(mime) { + mime_subparts_unbind(mime); /* Be sure it's not referenced anymore. */ + while(mime->firstpart) { + part = mime->firstpart; + mime->firstpart = part->nextpart; + Curl_mime_cleanpart(part); + free(part); + } + free(mime); + } +} + +CURLcode Curl_mime_duppart(struct Curl_easy *data, + curl_mimepart *dst, const curl_mimepart *src) +{ + curl_mime *mime; + curl_mimepart *d; + const curl_mimepart *s; + CURLcode res = CURLE_OK; + + DEBUGASSERT(dst); + + /* Duplicate content. */ + switch(src->kind) { + case MIMEKIND_NONE: + break; + case MIMEKIND_DATA: + res = curl_mime_data(dst, src->data, (size_t) src->datasize); + break; + case MIMEKIND_FILE: + res = curl_mime_filedata(dst, src->data); + /* Do not abort duplication if file is not readable. */ + if(res == CURLE_READ_ERROR) + res = CURLE_OK; + break; + case MIMEKIND_CALLBACK: + res = curl_mime_data_cb(dst, src->datasize, src->readfunc, + src->seekfunc, src->freefunc, src->arg); + break; + case MIMEKIND_MULTIPART: + /* No one knows about the cloned subparts, thus always attach ownership + to the part. */ + mime = curl_mime_init(data); + res = mime? curl_mime_subparts(dst, mime): CURLE_OUT_OF_MEMORY; + + /* Duplicate subparts. */ + for(s = ((curl_mime *) src->arg)->firstpart; !res && s; s = s->nextpart) { + d = curl_mime_addpart(mime); + res = d? Curl_mime_duppart(data, d, s): CURLE_OUT_OF_MEMORY; + } + break; + default: /* Invalid kind: should not occur. */ + res = CURLE_BAD_FUNCTION_ARGUMENT; /* Internal error? */ + break; + } + + /* Duplicate headers. */ + if(!res && src->userheaders) { + struct curl_slist *hdrs = Curl_slist_duplicate(src->userheaders); + + if(!hdrs) + res = CURLE_OUT_OF_MEMORY; + else { + /* No one but this procedure knows about the new header list, + so always take ownership. */ + res = curl_mime_headers(dst, hdrs, TRUE); + if(res) + curl_slist_free_all(hdrs); + } + } + + if(!res) { + /* Duplicate other fields. */ + dst->encoder = src->encoder; + res = curl_mime_type(dst, src->mimetype); + } + if(!res) + res = curl_mime_name(dst, src->name); + if(!res) + res = curl_mime_filename(dst, src->filename); + + /* If an error occurred, rollback. */ + if(res) + Curl_mime_cleanpart(dst); + + return res; +} + +/* + * Mime build functions. + */ + +/* Create a mime handle. */ +curl_mime *curl_mime_init(struct Curl_easy *easy) +{ + curl_mime *mime; + + mime = (curl_mime *) malloc(sizeof(*mime)); + + if(mime) { + mime->parent = NULL; + mime->firstpart = NULL; + mime->lastpart = NULL; + + memset(mime->boundary, '-', MIME_BOUNDARY_DASHES); + if(Curl_rand_alnum(easy, + (unsigned char *) &mime->boundary[MIME_BOUNDARY_DASHES], + MIME_RAND_BOUNDARY_CHARS + 1)) { + /* failed to get random separator, bail out */ + free(mime); + return NULL; + } + mimesetstate(&mime->state, MIMESTATE_BEGIN, NULL); + } + + return mime; +} + +/* Initialize a mime part. */ +void Curl_mime_initpart(curl_mimepart *part) +{ + memset((char *) part, 0, sizeof(*part)); + part->lastreadstatus = 1; /* Successful read status. */ + mimesetstate(&part->state, MIMESTATE_BEGIN, NULL); +} + +/* Create a mime part and append it to a mime handle's part list. */ +curl_mimepart *curl_mime_addpart(curl_mime *mime) +{ + curl_mimepart *part; + + if(!mime) + return NULL; + + part = (curl_mimepart *) malloc(sizeof(*part)); + + if(part) { + Curl_mime_initpart(part); + part->parent = mime; + + if(mime->lastpart) + mime->lastpart->nextpart = part; + else + mime->firstpart = part; + + mime->lastpart = part; + } + + return part; +} + +/* Set mime part name. */ +CURLcode curl_mime_name(curl_mimepart *part, const char *name) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + Curl_safefree(part->name); + + if(name) { + part->name = strdup(name); + if(!part->name) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +/* Set mime part remote file name. */ +CURLcode curl_mime_filename(curl_mimepart *part, const char *filename) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + Curl_safefree(part->filename); + + if(filename) { + part->filename = strdup(filename); + if(!part->filename) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +/* Set mime part content from memory data. */ +CURLcode curl_mime_data(curl_mimepart *part, + const char *data, size_t datasize) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + cleanup_part_content(part); + + if(data) { + if(datasize == CURL_ZERO_TERMINATED) + datasize = strlen(data); + + part->data = malloc(datasize + 1); + if(!part->data) + return CURLE_OUT_OF_MEMORY; + + part->datasize = datasize; + + if(datasize) + memcpy(part->data, data, datasize); + part->data[datasize] = '\0'; /* Set a null terminator as sentinel. */ + + part->readfunc = mime_mem_read; + part->seekfunc = mime_mem_seek; + part->freefunc = mime_mem_free; + part->flags |= MIME_FAST_READ; + part->kind = MIMEKIND_DATA; + } + + return CURLE_OK; +} + +/* Set mime part content from named local file. */ +CURLcode curl_mime_filedata(curl_mimepart *part, const char *filename) +{ + CURLcode result = CURLE_OK; + + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + cleanup_part_content(part); + + if(filename) { + char *base; + struct_stat sbuf; + + if(stat(filename, &sbuf) || access(filename, R_OK)) + result = CURLE_READ_ERROR; + + part->data = strdup(filename); + if(!part->data) + result = CURLE_OUT_OF_MEMORY; + + part->datasize = -1; + if(!result && S_ISREG(sbuf.st_mode)) { + part->datasize = filesize(filename, sbuf); + part->seekfunc = mime_file_seek; + } + + part->readfunc = mime_file_read; + part->freefunc = mime_file_free; + part->kind = MIMEKIND_FILE; + + /* As a side effect, set the filename to the current file's base name. + It is possible to withdraw this by explicitly calling + curl_mime_filename() with a NULL filename argument after the current + call. */ + base = strippath(filename); + if(!base) + result = CURLE_OUT_OF_MEMORY; + else { + CURLcode res = curl_mime_filename(part, base); + + if(res) + result = res; + free(base); + } + } + return result; +} + +/* Set mime part type. */ +CURLcode curl_mime_type(curl_mimepart *part, const char *mimetype) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + Curl_safefree(part->mimetype); + + if(mimetype) { + part->mimetype = strdup(mimetype); + if(!part->mimetype) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +/* Set mime data transfer encoder. */ +CURLcode curl_mime_encoder(curl_mimepart *part, const char *encoding) +{ + CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; + const struct mime_encoder *mep; + + if(!part) + return result; + + part->encoder = NULL; + + if(!encoding) + return CURLE_OK; /* Removing current encoder. */ + + for(mep = encoders; mep->name; mep++) + if(strcasecompare(encoding, mep->name)) { + part->encoder = mep; + result = CURLE_OK; + } + + return result; +} + +/* Set mime part headers. */ +CURLcode curl_mime_headers(curl_mimepart *part, + struct curl_slist *headers, int take_ownership) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(part->flags & MIME_USERHEADERS_OWNER) { + if(part->userheaders != headers) /* Allow setting twice the same list. */ + curl_slist_free_all(part->userheaders); + part->flags &= ~MIME_USERHEADERS_OWNER; + } + part->userheaders = headers; + if(headers && take_ownership) + part->flags |= MIME_USERHEADERS_OWNER; + return CURLE_OK; +} + +/* Set mime part content from callback. */ +CURLcode curl_mime_data_cb(curl_mimepart *part, curl_off_t datasize, + curl_read_callback readfunc, + curl_seek_callback seekfunc, + curl_free_callback freefunc, void *arg) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + cleanup_part_content(part); + + if(readfunc) { + part->readfunc = readfunc; + part->seekfunc = seekfunc; + part->freefunc = freefunc; + part->arg = arg; + part->datasize = datasize; + part->kind = MIMEKIND_CALLBACK; + } + + return CURLE_OK; +} + +/* Set mime part content from subparts. */ +CURLcode Curl_mime_set_subparts(curl_mimepart *part, + curl_mime *subparts, int take_ownership) +{ + curl_mime *root; + + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + /* Accept setting twice the same subparts. */ + if(part->kind == MIMEKIND_MULTIPART && part->arg == subparts) + return CURLE_OK; + + cleanup_part_content(part); + + if(subparts) { + /* Should not have been attached already. */ + if(subparts->parent) + return CURLE_BAD_FUNCTION_ARGUMENT; + + /* Should not be the part's root. */ + root = part->parent; + if(root) { + while(root->parent && root->parent->parent) + root = root->parent->parent; + if(subparts == root) { + /* Can't add as a subpart of itself. */ + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } + + subparts->parent = part; + /* Subparts are processed internally: no read callback. */ + part->seekfunc = mime_subparts_seek; + part->freefunc = take_ownership? mime_subparts_free: mime_subparts_unbind; + part->arg = subparts; + part->datasize = -1; + part->kind = MIMEKIND_MULTIPART; + } + + return CURLE_OK; +} + +CURLcode curl_mime_subparts(curl_mimepart *part, curl_mime *subparts) +{ + return Curl_mime_set_subparts(part, subparts, TRUE); +} + + +/* Readback from top mime. */ +/* Argument is the dummy top part. */ +size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream) +{ + curl_mimepart *part = (curl_mimepart *) instream; + size_t ret; + bool hasread; + + (void) size; /* Always 1. */ + + do { + hasread = FALSE; + ret = readback_part(part, buffer, nitems, &hasread); + /* + * If this is not possible to get some data without calling more than + * one read callback (probably because a content encoder is not able to + * deliver a new bunch for the few data accumulated so far), force another + * read until we get enough data or a special exit code. + */ + } while(ret == STOP_FILLING); + + return ret; +} + +/* Rewind mime stream. */ +CURLcode Curl_mime_rewind(curl_mimepart *part) +{ + return mime_part_rewind(part) == CURL_SEEKFUNC_OK? + CURLE_OK: CURLE_SEND_FAIL_REWIND; +} + +/* Compute header list size. */ +static size_t slist_size(struct curl_slist *s, + size_t overhead, const char *skip, size_t skiplen) +{ + size_t size = 0; + + for(; s; s = s->next) + if(!skip || !match_header(s, skip, skiplen)) + size += strlen(s->data) + overhead; + return size; +} + +/* Get/compute multipart size. */ +static curl_off_t multipart_size(curl_mime *mime) +{ + curl_off_t size; + curl_off_t boundarysize; + curl_mimepart *part; + + if(!mime) + return 0; /* Not present -> empty. */ + + boundarysize = 4 + MIME_BOUNDARY_LEN + 2; + size = boundarysize; /* Final boundary - CRLF after headers. */ + + for(part = mime->firstpart; part; part = part->nextpart) { + curl_off_t sz = Curl_mime_size(part); + + if(sz < 0) + size = sz; + + if(size >= 0) + size += boundarysize + sz; + } + + return size; +} + +/* Get/compute mime size. */ +curl_off_t Curl_mime_size(curl_mimepart *part) +{ + curl_off_t size; + + if(part->kind == MIMEKIND_MULTIPART) + part->datasize = multipart_size(part->arg); + + size = part->datasize; + + if(part->encoder) + size = part->encoder->sizefunc(part); + + if(size >= 0 && !(part->flags & MIME_BODY_ONLY)) { + /* Compute total part size. */ + size += slist_size(part->curlheaders, 2, NULL, 0); + size += slist_size(part->userheaders, 2, STRCONST("Content-Type")); + size += 2; /* CRLF after headers. */ + } + return size; +} + +/* Add a header. */ +/* VARARGS2 */ +CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...) +{ + struct curl_slist *hdr = NULL; + char *s = NULL; + va_list ap; + + va_start(ap, fmt); + s = curl_mvaprintf(fmt, ap); + va_end(ap); + + if(s) { + hdr = Curl_slist_append_nodup(*slp, s); + if(hdr) + *slp = hdr; + else + free(s); + } + + return hdr? CURLE_OK: CURLE_OUT_OF_MEMORY; +} + +/* Add a content type header. */ +static CURLcode add_content_type(struct curl_slist **slp, + const char *type, const char *boundary) +{ + return Curl_mime_add_header(slp, "Content-Type: %s%s%s", type, + boundary? "; boundary=": "", + boundary? boundary: ""); +} + +const char *Curl_mime_contenttype(const char *filename) +{ + /* + * If no content type was specified, we scan through a few well-known + * extensions and pick the first we match! + */ + struct ContentType { + const char *extension; + const char *type; + }; + static const struct ContentType ctts[] = { + {".gif", "image/gif"}, + {".jpg", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".png", "image/png"}, + {".svg", "image/svg+xml"}, + {".txt", "text/plain"}, + {".htm", "text/html"}, + {".html", "text/html"}, + {".pdf", "application/pdf"}, + {".xml", "application/xml"} + }; + + if(filename) { + size_t len1 = strlen(filename); + const char *nameend = filename + len1; + unsigned int i; + + for(i = 0; i < sizeof(ctts) / sizeof(ctts[0]); i++) { + size_t len2 = strlen(ctts[i].extension); + + if(len1 >= len2 && strcasecompare(nameend - len2, ctts[i].extension)) + return ctts[i].type; + } + } + return NULL; +} + +static bool content_type_match(const char *contenttype, + const char *target, size_t len) +{ + if(contenttype && strncasecompare(contenttype, target, len)) + switch(contenttype[len]) { + case '\0': + case '\t': + case '\r': + case '\n': + case ' ': + case ';': + return TRUE; + } + return FALSE; +} + +CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, + curl_mimepart *part, + const char *contenttype, + const char *disposition, + enum mimestrategy strategy) +{ + curl_mime *mime = NULL; + const char *boundary = NULL; + char *customct; + const char *cte = NULL; + CURLcode ret = CURLE_OK; + + /* Get rid of previously prepared headers. */ + curl_slist_free_all(part->curlheaders); + part->curlheaders = NULL; + + /* Be sure we won't access old headers later. */ + if(part->state.state == MIMESTATE_CURLHEADERS) + mimesetstate(&part->state, MIMESTATE_CURLHEADERS, NULL); + + /* Check if content type is specified. */ + customct = part->mimetype; + if(!customct) + customct = search_header(part->userheaders, STRCONST("Content-Type")); + if(customct) + contenttype = customct; + + /* If content type is not specified, try to determine it. */ + if(!contenttype) { + switch(part->kind) { + case MIMEKIND_MULTIPART: + contenttype = MULTIPART_CONTENTTYPE_DEFAULT; + break; + case MIMEKIND_FILE: + contenttype = Curl_mime_contenttype(part->filename); + if(!contenttype) + contenttype = Curl_mime_contenttype(part->data); + if(!contenttype && part->filename) + contenttype = FILE_CONTENTTYPE_DEFAULT; + break; + default: + contenttype = Curl_mime_contenttype(part->filename); + break; + } + } + + if(part->kind == MIMEKIND_MULTIPART) { + mime = (curl_mime *) part->arg; + if(mime) + boundary = mime->boundary; + } + else if(contenttype && !customct && + content_type_match(contenttype, STRCONST("text/plain"))) + if(strategy == MIMESTRATEGY_MAIL || !part->filename) + contenttype = NULL; + + /* Issue content-disposition header only if not already set by caller. */ + if(!search_header(part->userheaders, STRCONST("Content-Disposition"))) { + if(!disposition) + if(part->filename || part->name || + (contenttype && !strncasecompare(contenttype, "multipart/", 10))) + disposition = DISPOSITION_DEFAULT; + if(disposition && curl_strequal(disposition, "attachment") && + !part->name && !part->filename) + disposition = NULL; + if(disposition) { + char *name = NULL; + char *filename = NULL; + + if(part->name) { + name = escape_string(data, part->name, strategy); + if(!name) + ret = CURLE_OUT_OF_MEMORY; + } + if(!ret && part->filename) { + filename = escape_string(data, part->filename, strategy); + if(!filename) + ret = CURLE_OUT_OF_MEMORY; + } + if(!ret) + ret = Curl_mime_add_header(&part->curlheaders, + "Content-Disposition: %s%s%s%s%s%s%s", + disposition, + name? "; name=\"": "", + name? name: "", + name? "\"": "", + filename? "; filename=\"": "", + filename? filename: "", + filename? "\"": ""); + Curl_safefree(name); + Curl_safefree(filename); + if(ret) + return ret; + } + } + + /* Issue Content-Type header. */ + if(contenttype) { + ret = add_content_type(&part->curlheaders, contenttype, boundary); + if(ret) + return ret; + } + + /* Content-Transfer-Encoding header. */ + if(!search_header(part->userheaders, + STRCONST("Content-Transfer-Encoding"))) { + if(part->encoder) + cte = part->encoder->name; + else if(contenttype && strategy == MIMESTRATEGY_MAIL && + part->kind != MIMEKIND_MULTIPART) + cte = "8bit"; + if(cte) { + ret = Curl_mime_add_header(&part->curlheaders, + "Content-Transfer-Encoding: %s", cte); + if(ret) + return ret; + } + } + + /* If we were reading curl-generated headers, restart with new ones (this + should not occur). */ + if(part->state.state == MIMESTATE_CURLHEADERS) + mimesetstate(&part->state, MIMESTATE_CURLHEADERS, part->curlheaders); + + /* Process subparts. */ + if(part->kind == MIMEKIND_MULTIPART && mime) { + curl_mimepart *subpart; + + disposition = NULL; + if(content_type_match(contenttype, STRCONST("multipart/form-data"))) + disposition = "form-data"; + for(subpart = mime->firstpart; subpart; subpart = subpart->nextpart) { + ret = Curl_mime_prepare_headers(data, subpart, NULL, + disposition, strategy); + if(ret) + return ret; + } + } + return ret; +} + +/* Recursively reset paused status in the given part. */ +void Curl_mime_unpause(curl_mimepart *part) +{ + if(part) { + if(part->lastreadstatus == CURL_READFUNC_PAUSE) + part->lastreadstatus = 1; /* Successful read status. */ + if(part->kind == MIMEKIND_MULTIPART) { + curl_mime *mime = (curl_mime *) part->arg; + + if(mime) { + curl_mimepart *subpart; + + for(subpart = mime->firstpart; subpart; subpart = subpart->nextpart) + Curl_mime_unpause(subpart); + } + } + } +} + + +#else /* !CURL_DISABLE_MIME && (!CURL_DISABLE_HTTP || + !CURL_DISABLE_SMTP || !CURL_DISABLE_IMAP) */ + +/* Mime not compiled in: define stubs for externally-referenced functions. */ +curl_mime *curl_mime_init(CURL *easy) +{ + (void) easy; + return NULL; +} + +void curl_mime_free(curl_mime *mime) +{ + (void) mime; +} + +curl_mimepart *curl_mime_addpart(curl_mime *mime) +{ + (void) mime; + return NULL; +} + +CURLcode curl_mime_name(curl_mimepart *part, const char *name) +{ + (void) part; + (void) name; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_filename(curl_mimepart *part, const char *filename) +{ + (void) part; + (void) filename; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_type(curl_mimepart *part, const char *mimetype) +{ + (void) part; + (void) mimetype; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_encoder(curl_mimepart *part, const char *encoding) +{ + (void) part; + (void) encoding; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_data(curl_mimepart *part, + const char *data, size_t datasize) +{ + (void) part; + (void) data; + (void) datasize; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_filedata(curl_mimepart *part, const char *filename) +{ + (void) part; + (void) filename; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_data_cb(curl_mimepart *part, + curl_off_t datasize, + curl_read_callback readfunc, + curl_seek_callback seekfunc, + curl_free_callback freefunc, + void *arg) +{ + (void) part; + (void) datasize; + (void) readfunc; + (void) seekfunc; + (void) freefunc; + (void) arg; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_subparts(curl_mimepart *part, curl_mime *subparts) +{ + (void) part; + (void) subparts; + return CURLE_NOT_BUILT_IN; +} + +CURLcode curl_mime_headers(curl_mimepart *part, + struct curl_slist *headers, int take_ownership) +{ + (void) part; + (void) headers; + (void) take_ownership; + return CURLE_NOT_BUILT_IN; +} + +CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...) +{ + (void)slp; + (void)fmt; + return CURLE_NOT_BUILT_IN; +} + +#endif /* if disabled */ diff --git a/Utilities/cmcurl/lib/mime.h b/Utilities/cmcurl/lib/mime.h new file mode 100644 index 0000000..0a05c2a --- /dev/null +++ b/Utilities/cmcurl/lib/mime.h @@ -0,0 +1,174 @@ +#ifndef HEADER_CURL_MIME_H +#define HEADER_CURL_MIME_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#define MIME_BOUNDARY_DASHES 24 /* leading boundary dashes */ +#define MIME_RAND_BOUNDARY_CHARS 22 /* Nb. of random boundary chars. */ +#define MAX_ENCODED_LINE_LENGTH 76 /* Maximum encoded line length. */ +#define ENCODING_BUFFER_SIZE 256 /* Encoding temp buffers size. */ + +/* Part flags. */ +#define MIME_USERHEADERS_OWNER (1 << 0) +#define MIME_BODY_ONLY (1 << 1) +#define MIME_FAST_READ (1 << 2) + +#define FILE_CONTENTTYPE_DEFAULT "application/octet-stream" +#define MULTIPART_CONTENTTYPE_DEFAULT "multipart/mixed" +#define DISPOSITION_DEFAULT "attachment" + +/* Part source kinds. */ +enum mimekind { + MIMEKIND_NONE = 0, /* Part not set. */ + MIMEKIND_DATA, /* Allocated mime data. */ + MIMEKIND_FILE, /* Data from file. */ + MIMEKIND_CALLBACK, /* Data from `read' callback. */ + MIMEKIND_MULTIPART, /* Data is a mime subpart. */ + MIMEKIND_LAST +}; + +/* Readback state tokens. */ +enum mimestate { + MIMESTATE_BEGIN, /* Readback has not yet started. */ + MIMESTATE_CURLHEADERS, /* In curl-generated headers. */ + MIMESTATE_USERHEADERS, /* In caller's supplied headers. */ + MIMESTATE_EOH, /* End of headers. */ + MIMESTATE_BODY, /* Placeholder. */ + MIMESTATE_BOUNDARY1, /* In boundary prefix. */ + MIMESTATE_BOUNDARY2, /* In boundary. */ + MIMESTATE_CONTENT, /* In content. */ + MIMESTATE_END, /* End of part reached. */ + MIMESTATE_LAST +}; + +/* Mime headers strategies. */ +enum mimestrategy { + MIMESTRATEGY_MAIL, /* Mime mail. */ + MIMESTRATEGY_FORM, /* HTTP post form. */ + MIMESTRATEGY_LAST +}; + +/* Content transfer encoder. */ +struct mime_encoder { + const char * name; /* Encoding name. */ + size_t (*encodefunc)(char *buffer, size_t size, bool ateof, + curl_mimepart *part); /* Encoded read. */ + curl_off_t (*sizefunc)(curl_mimepart *part); /* Encoded size. */ +}; + +/* Content transfer encoder state. */ +struct mime_encoder_state { + size_t pos; /* Position on output line. */ + size_t bufbeg; /* Next data index in input buffer. */ + size_t bufend; /* First unused byte index in input buffer. */ + char buf[ENCODING_BUFFER_SIZE]; /* Input buffer. */ +}; + +/* Mime readback state. */ +struct mime_state { + enum mimestate state; /* Current state token. */ + void *ptr; /* State-dependent pointer. */ + curl_off_t offset; /* State-dependent offset. */ +}; + +/* Boundary string length. */ +#define MIME_BOUNDARY_LEN (MIME_BOUNDARY_DASHES + MIME_RAND_BOUNDARY_CHARS) + +/* A mime multipart. */ +struct curl_mime { + curl_mimepart *parent; /* Parent part. */ + curl_mimepart *firstpart; /* First part. */ + curl_mimepart *lastpart; /* Last part. */ + char boundary[MIME_BOUNDARY_LEN + 1]; /* The part boundary. */ + struct mime_state state; /* Current readback state. */ +}; + +/* A mime part. */ +struct curl_mimepart { + curl_mime *parent; /* Parent mime structure. */ + curl_mimepart *nextpart; /* Forward linked list. */ + enum mimekind kind; /* The part kind. */ + unsigned int flags; /* Flags. */ + char *data; /* Memory data or file name. */ + curl_read_callback readfunc; /* Read function. */ + curl_seek_callback seekfunc; /* Seek function. */ + curl_free_callback freefunc; /* Argument free function. */ + void *arg; /* Argument to callback functions. */ + FILE *fp; /* File pointer. */ + struct curl_slist *curlheaders; /* Part headers. */ + struct curl_slist *userheaders; /* Part headers. */ + char *mimetype; /* Part mime type. */ + char *filename; /* Remote file name. */ + char *name; /* Data name. */ + curl_off_t datasize; /* Expected data size. */ + struct mime_state state; /* Current readback state. */ + const struct mime_encoder *encoder; /* Content data encoder. */ + struct mime_encoder_state encstate; /* Data encoder state. */ + size_t lastreadstatus; /* Last read callback returned status. */ +}; + +CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...); + +#if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ + !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_IMAP)) + +/* Prototypes. */ +void Curl_mime_initpart(struct curl_mimepart *part); +void Curl_mime_cleanpart(struct curl_mimepart *part); +CURLcode Curl_mime_duppart(struct Curl_easy *data, + struct curl_mimepart *dst, + const curl_mimepart *src); +CURLcode Curl_mime_set_subparts(struct curl_mimepart *part, + struct curl_mime *subparts, + int take_ownership); +CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, + struct curl_mimepart *part, + const char *contenttype, + const char *disposition, + enum mimestrategy strategy); +curl_off_t Curl_mime_size(struct curl_mimepart *part); +size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, + void *instream); +CURLcode Curl_mime_rewind(struct curl_mimepart *part); +const char *Curl_mime_contenttype(const char *filename); +void Curl_mime_unpause(struct curl_mimepart *part); + +#else +/* if disabled */ +#define Curl_mime_initpart(x) +#define Curl_mime_cleanpart(x) +#define Curl_mime_duppart(x,y,z) CURLE_OK /* Nothing to duplicate. Succeed */ +#define Curl_mime_set_subparts(a,b,c) CURLE_NOT_BUILT_IN +#define Curl_mime_prepare_headers(a,b,c,d,e) CURLE_NOT_BUILT_IN +#define Curl_mime_size(x) (curl_off_t) -1 +#define Curl_mime_read NULL +#define Curl_mime_rewind(x) ((void)x, CURLE_NOT_BUILT_IN) +#define Curl_mime_unpause(x) +#endif + + +#endif /* HEADER_CURL_MIME_H */ diff --git a/Utilities/cmcurl/lib/mprintf.c b/Utilities/cmcurl/lib/mprintf.c new file mode 100644 index 0000000..6b5df5b --- /dev/null +++ b/Utilities/cmcurl/lib/mprintf.c @@ -0,0 +1,1172 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * + * Purpose: + * A merge of Bjorn Reese's format() function and Daniel's dsprintf() + * 1.0. A full blooded printf() clone with full support for <num>$ + * everywhere (parameters, widths and precisions) including variabled + * sized parameters (like doubles, long longs, long doubles and even + * void * in 64-bit architectures). + * + * Current restrictions: + * - Max 128 parameters + * - No 'long double' support. + * + * If you ever want truly portable and good *printf() clones, the project that + * took on from here is named 'Trio' and you find more details on the trio web + * page at https://daniel.haxx.se/projects/trio/ + */ + +#include "curl_setup.h" +#include "dynbuf.h" +#include <curl/mprintf.h> + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/* + * If SIZEOF_SIZE_T has not been defined, default to the size of long. + */ + +#ifdef HAVE_LONGLONG +# define LONG_LONG_TYPE long long +# define HAVE_LONG_LONG_TYPE +#else +# if defined(_MSC_VER) && (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) +# define LONG_LONG_TYPE __int64 +# define HAVE_LONG_LONG_TYPE +# else +# undef LONG_LONG_TYPE +# undef HAVE_LONG_LONG_TYPE +# endif +#endif + +/* + * Non-ANSI integer extensions + */ + +#if (defined(_WIN32_WCE)) || \ + (defined(__MINGW32__)) || \ + (defined(_MSC_VER) && (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64)) +# define MP_HAVE_INT_EXTENSIONS +#endif + +/* + * Max integer data types that mprintf.c is capable + */ + +#ifdef HAVE_LONG_LONG_TYPE +# define mp_intmax_t LONG_LONG_TYPE +# define mp_uintmax_t unsigned LONG_LONG_TYPE +#else +# define mp_intmax_t long +# define mp_uintmax_t unsigned long +#endif + +#define BUFFSIZE 326 /* buffer for long-to-str and float-to-str calcs, should + fit negative DBL_MAX (317 letters) */ +#define MAX_PARAMETERS 128 /* lame static limit */ + +#ifdef __AMIGA__ +# undef FORMAT_INT +#endif + +/* Lower-case digits. */ +static const char lower_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + +/* Upper-case digits. */ +static const char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +#define OUTCHAR(x) \ + do { \ + if(stream((unsigned char)(x), (FILE *)data) != -1) \ + done++; \ + else \ + return done; /* return immediately on failure */ \ + } while(0) + +/* Data type to read from the arglist */ +typedef enum { + FORMAT_UNKNOWN = 0, + FORMAT_STRING, + FORMAT_PTR, + FORMAT_INT, + FORMAT_INTPTR, + FORMAT_LONG, + FORMAT_LONGLONG, + FORMAT_DOUBLE, + FORMAT_LONGDOUBLE, + FORMAT_WIDTH /* For internal use */ +} FormatType; + +/* conversion and display flags */ +enum { + FLAGS_NEW = 0, + FLAGS_SPACE = 1<<0, + FLAGS_SHOWSIGN = 1<<1, + FLAGS_LEFT = 1<<2, + FLAGS_ALT = 1<<3, + FLAGS_SHORT = 1<<4, + FLAGS_LONG = 1<<5, + FLAGS_LONGLONG = 1<<6, + FLAGS_LONGDOUBLE = 1<<7, + FLAGS_PAD_NIL = 1<<8, + FLAGS_UNSIGNED = 1<<9, + FLAGS_OCTAL = 1<<10, + FLAGS_HEX = 1<<11, + FLAGS_UPPER = 1<<12, + FLAGS_WIDTH = 1<<13, /* '*' or '*<num>$' used */ + FLAGS_WIDTHPARAM = 1<<14, /* width PARAMETER was specified */ + FLAGS_PREC = 1<<15, /* precision was specified */ + FLAGS_PRECPARAM = 1<<16, /* precision PARAMETER was specified */ + FLAGS_CHAR = 1<<17, /* %c story */ + FLAGS_FLOATE = 1<<18, /* %e or %E */ + FLAGS_FLOATG = 1<<19 /* %g or %G */ +}; + +struct va_stack { + FormatType type; + int flags; + long width; /* width OR width parameter number */ + long precision; /* precision OR precision parameter number */ + union { + char *str; + void *ptr; + union { + mp_intmax_t as_signed; + mp_uintmax_t as_unsigned; + } num; + double dnum; + } data; +}; + +struct nsprintf { + char *buffer; + size_t length; + size_t max; +}; + +struct asprintf { + struct dynbuf *b; + bool fail; /* if an alloc has failed and thus the output is not the complete + data */ +}; + +static long dprintf_DollarString(char *input, char **end) +{ + int number = 0; + while(ISDIGIT(*input)) { + if(number < MAX_PARAMETERS) { + number *= 10; + number += *input - '0'; + } + input++; + } + if(number <= MAX_PARAMETERS && ('$' == *input)) { + *end = ++input; + return number; + } + return 0; +} + +static bool dprintf_IsQualifierNoDollar(const char *fmt) +{ +#if defined(MP_HAVE_INT_EXTENSIONS) + if(!strncmp(fmt, "I32", 3) || !strncmp(fmt, "I64", 3)) { + return TRUE; + } +#endif + + switch(*fmt) { + case '-': case '+': case ' ': case '#': case '.': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'h': case 'l': case 'L': case 'z': case 'q': + case '*': case 'O': +#if defined(MP_HAVE_INT_EXTENSIONS) + case 'I': +#endif + return TRUE; + + default: + return FALSE; + } +} + +/****************************************************************** + * + * Pass 1: + * Create an index with the type of each parameter entry and its + * value (may vary in size) + * + * Returns zero on success. + * + ******************************************************************/ + +static int dprintf_Pass1(const char *format, struct va_stack *vto, + char **endpos, va_list arglist) +{ + char *fmt = (char *)format; + int param_num = 0; + long this_param; + long width; + long precision; + int flags; + long max_param = 0; + long i; + + while(*fmt) { + if(*fmt++ == '%') { + if(*fmt == '%') { + fmt++; + continue; /* while */ + } + + flags = FLAGS_NEW; + + /* Handle the positional case (N$) */ + + param_num++; + + this_param = dprintf_DollarString(fmt, &fmt); + if(0 == this_param) + /* we got no positional, get the next counter */ + this_param = param_num; + + if(this_param > max_param) + max_param = this_param; + + /* + * The parameter with number 'i' should be used. Next, we need + * to get SIZE and TYPE of the parameter. Add the information + * to our array. + */ + + width = 0; + precision = 0; + + /* Handle the flags */ + + while(dprintf_IsQualifierNoDollar(fmt)) { +#if defined(MP_HAVE_INT_EXTENSIONS) + if(!strncmp(fmt, "I32", 3)) { + flags |= FLAGS_LONG; + fmt += 3; + } + else if(!strncmp(fmt, "I64", 3)) { + flags |= FLAGS_LONGLONG; + fmt += 3; + } + else +#endif + + switch(*fmt++) { + case ' ': + flags |= FLAGS_SPACE; + break; + case '+': + flags |= FLAGS_SHOWSIGN; + break; + case '-': + flags |= FLAGS_LEFT; + flags &= ~FLAGS_PAD_NIL; + break; + case '#': + flags |= FLAGS_ALT; + break; + case '.': + if('*' == *fmt) { + /* The precision is picked from a specified parameter */ + + flags |= FLAGS_PRECPARAM; + fmt++; + param_num++; + + i = dprintf_DollarString(fmt, &fmt); + if(i) + precision = i; + else + precision = param_num; + + if(precision > max_param) + max_param = precision; + } + else { + flags |= FLAGS_PREC; + precision = strtol(fmt, &fmt, 10); + } + if((flags & (FLAGS_PREC | FLAGS_PRECPARAM)) == + (FLAGS_PREC | FLAGS_PRECPARAM)) + /* it is not permitted to use both kinds of precision for the same + argument */ + return 1; + break; + case 'h': + flags |= FLAGS_SHORT; + break; +#if defined(MP_HAVE_INT_EXTENSIONS) + case 'I': +#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) + flags |= FLAGS_LONGLONG; +#else + flags |= FLAGS_LONG; +#endif + break; +#endif + case 'l': + if(flags & FLAGS_LONG) + flags |= FLAGS_LONGLONG; + else + flags |= FLAGS_LONG; + break; + case 'L': + flags |= FLAGS_LONGDOUBLE; + break; + case 'q': + flags |= FLAGS_LONGLONG; + break; + case 'z': + /* the code below generates a warning if -Wunreachable-code is + used */ +#if (SIZEOF_SIZE_T > SIZEOF_LONG) + flags |= FLAGS_LONGLONG; +#else + flags |= FLAGS_LONG; +#endif + break; + case 'O': +#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) + flags |= FLAGS_LONGLONG; +#else + flags |= FLAGS_LONG; +#endif + break; + case '0': + if(!(flags & FLAGS_LEFT)) + flags |= FLAGS_PAD_NIL; + /* FALLTHROUGH */ + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + flags |= FLAGS_WIDTH; + width = strtol(fmt-1, &fmt, 10); + break; + case '*': /* Special case */ + flags |= FLAGS_WIDTHPARAM; + param_num++; + + i = dprintf_DollarString(fmt, &fmt); + if(i) + width = i; + else + width = param_num; + if(width > max_param) + max_param = width; + break; + case '\0': + fmt--; + default: + break; + } + } /* switch */ + + /* Handle the specifier */ + + i = this_param - 1; + + if((i < 0) || (i >= MAX_PARAMETERS)) + /* out of allowed range */ + return 1; + + switch(*fmt) { + case 'S': + flags |= FLAGS_ALT; + /* FALLTHROUGH */ + case 's': + vto[i].type = FORMAT_STRING; + break; + case 'n': + vto[i].type = FORMAT_INTPTR; + break; + case 'p': + vto[i].type = FORMAT_PTR; + break; + case 'd': case 'i': + vto[i].type = FORMAT_INT; + break; + case 'u': + vto[i].type = FORMAT_INT; + flags |= FLAGS_UNSIGNED; + break; + case 'o': + vto[i].type = FORMAT_INT; + flags |= FLAGS_OCTAL; + break; + case 'x': + vto[i].type = FORMAT_INT; + flags |= FLAGS_HEX|FLAGS_UNSIGNED; + break; + case 'X': + vto[i].type = FORMAT_INT; + flags |= FLAGS_HEX|FLAGS_UPPER|FLAGS_UNSIGNED; + break; + case 'c': + vto[i].type = FORMAT_INT; + flags |= FLAGS_CHAR; + break; + case 'f': + vto[i].type = FORMAT_DOUBLE; + break; + case 'e': + vto[i].type = FORMAT_DOUBLE; + flags |= FLAGS_FLOATE; + break; + case 'E': + vto[i].type = FORMAT_DOUBLE; + flags |= FLAGS_FLOATE|FLAGS_UPPER; + break; + case 'g': + vto[i].type = FORMAT_DOUBLE; + flags |= FLAGS_FLOATG; + break; + case 'G': + vto[i].type = FORMAT_DOUBLE; + flags |= FLAGS_FLOATG|FLAGS_UPPER; + break; + default: + vto[i].type = FORMAT_UNKNOWN; + break; + } /* switch */ + + vto[i].flags = flags; + vto[i].width = width; + vto[i].precision = precision; + + if(flags & FLAGS_WIDTHPARAM) { + /* we have the width specified from a parameter, so we make that + parameter's info setup properly */ + long k = width - 1; + if((k < 0) || (k >= MAX_PARAMETERS)) + /* out of allowed range */ + return 1; + vto[i].width = k; + vto[k].type = FORMAT_WIDTH; + vto[k].flags = FLAGS_NEW; + /* can't use width or precision of width! */ + vto[k].width = 0; + vto[k].precision = 0; + } + if(flags & FLAGS_PRECPARAM) { + /* we have the precision specified from a parameter, so we make that + parameter's info setup properly */ + long k = precision - 1; + if((k < 0) || (k >= MAX_PARAMETERS)) + /* out of allowed range */ + return 1; + vto[i].precision = k; + vto[k].type = FORMAT_WIDTH; + vto[k].flags = FLAGS_NEW; + /* can't use width or precision of width! */ + vto[k].width = 0; + vto[k].precision = 0; + } + *endpos++ = fmt + ((*fmt == '\0') ? 0 : 1); /* end of this sequence */ + } + } + + /* Read the arg list parameters into our data list */ + for(i = 0; i<max_param; i++) { + /* Width/precision arguments must be read before the main argument + they are attached to */ + if(vto[i].flags & FLAGS_WIDTHPARAM) { + vto[vto[i].width].data.num.as_signed = + (mp_intmax_t)va_arg(arglist, int); + } + if(vto[i].flags & FLAGS_PRECPARAM) { + vto[vto[i].precision].data.num.as_signed = + (mp_intmax_t)va_arg(arglist, int); + } + + switch(vto[i].type) { + case FORMAT_STRING: + vto[i].data.str = va_arg(arglist, char *); + break; + + case FORMAT_INTPTR: + case FORMAT_UNKNOWN: + case FORMAT_PTR: + vto[i].data.ptr = va_arg(arglist, void *); + break; + + case FORMAT_INT: +#ifdef HAVE_LONG_LONG_TYPE + if((vto[i].flags & FLAGS_LONGLONG) && (vto[i].flags & FLAGS_UNSIGNED)) + vto[i].data.num.as_unsigned = + (mp_uintmax_t)va_arg(arglist, mp_uintmax_t); + else if(vto[i].flags & FLAGS_LONGLONG) + vto[i].data.num.as_signed = + (mp_intmax_t)va_arg(arglist, mp_intmax_t); + else +#endif + { + if((vto[i].flags & FLAGS_LONG) && (vto[i].flags & FLAGS_UNSIGNED)) + vto[i].data.num.as_unsigned = + (mp_uintmax_t)va_arg(arglist, unsigned long); + else if(vto[i].flags & FLAGS_LONG) + vto[i].data.num.as_signed = + (mp_intmax_t)va_arg(arglist, long); + else if(vto[i].flags & FLAGS_UNSIGNED) + vto[i].data.num.as_unsigned = + (mp_uintmax_t)va_arg(arglist, unsigned int); + else + vto[i].data.num.as_signed = + (mp_intmax_t)va_arg(arglist, int); + } + break; + + case FORMAT_DOUBLE: + vto[i].data.dnum = va_arg(arglist, double); + break; + + case FORMAT_WIDTH: + /* Argument has been read. Silently convert it into an integer + * for later use + */ + vto[i].type = FORMAT_INT; + break; + + default: + break; + } + } + + return 0; + +} + +static int dprintf_formatf( + void *data, /* untouched by format(), just sent to the stream() function in + the second argument */ + /* function pointer called for each output character */ + int (*stream)(int, FILE *), + const char *format, /* %-formatted string */ + va_list ap_save) /* list of parameters */ +{ + /* Base-36 digits for numbers. */ + const char *digits = lower_digits; + + /* Pointer into the format string. */ + char *f; + + /* Number of characters written. */ + int done = 0; + + long param; /* current parameter to read */ + long param_num = 0; /* parameter counter */ + + struct va_stack vto[MAX_PARAMETERS]; + char *endpos[MAX_PARAMETERS]; + char **end; + char work[BUFFSIZE]; + struct va_stack *p; + + /* 'workend' points to the final buffer byte position, but with an extra + byte as margin to avoid the (false?) warning Coverity gives us + otherwise */ + char *workend = &work[sizeof(work) - 2]; + + /* Do the actual %-code parsing */ + if(dprintf_Pass1(format, vto, endpos, ap_save)) + return 0; + + end = &endpos[0]; /* the initial end-position from the list dprintf_Pass1() + created for us */ + + f = (char *)format; + while(*f != '\0') { + /* Format spec modifiers. */ + int is_alt; + + /* Width of a field. */ + long width; + + /* Precision of a field. */ + long prec; + + /* Decimal integer is negative. */ + int is_neg; + + /* Base of a number to be written. */ + unsigned long base; + + /* Integral values to be written. */ + mp_uintmax_t num; + + /* Used to convert negative in positive. */ + mp_intmax_t signed_num; + + char *w; + + if(*f != '%') { + /* This isn't a format spec, so write everything out until the next one + OR end of string is reached. */ + do { + OUTCHAR(*f); + } while(*++f && ('%' != *f)); + continue; + } + + ++f; + + /* Check for "%%". Note that although the ANSI standard lists + '%' as a conversion specifier, it says "The complete format + specification shall be `%%'," so we can avoid all the width + and precision processing. */ + if(*f == '%') { + ++f; + OUTCHAR('%'); + continue; + } + + /* If this is a positional parameter, the position must follow immediately + after the %, thus create a %<num>$ sequence */ + param = dprintf_DollarString(f, &f); + + if(!param) + param = param_num; + else + --param; + + param_num++; /* increase this always to allow "%2$s %1$s %s" and then the + third %s will pick the 3rd argument */ + + p = &vto[param]; + + /* pick up the specified width */ + if(p->flags & FLAGS_WIDTHPARAM) { + width = (long)vto[p->width].data.num.as_signed; + param_num++; /* since the width is extracted from a parameter, we + must skip that to get to the next one properly */ + if(width < 0) { + /* "A negative field width is taken as a '-' flag followed by a + positive field width." */ + width = -width; + p->flags |= FLAGS_LEFT; + p->flags &= ~FLAGS_PAD_NIL; + } + } + else + width = p->width; + + /* pick up the specified precision */ + if(p->flags & FLAGS_PRECPARAM) { + prec = (long)vto[p->precision].data.num.as_signed; + param_num++; /* since the precision is extracted from a parameter, we + must skip that to get to the next one properly */ + if(prec < 0) + /* "A negative precision is taken as if the precision were + omitted." */ + prec = -1; + } + else if(p->flags & FLAGS_PREC) + prec = p->precision; + else + prec = -1; + + is_alt = (p->flags & FLAGS_ALT) ? 1 : 0; + + switch(p->type) { + case FORMAT_INT: + num = p->data.num.as_unsigned; + if(p->flags & FLAGS_CHAR) { + /* Character. */ + if(!(p->flags & FLAGS_LEFT)) + while(--width > 0) + OUTCHAR(' '); + OUTCHAR((char) num); + if(p->flags & FLAGS_LEFT) + while(--width > 0) + OUTCHAR(' '); + break; + } + if(p->flags & FLAGS_OCTAL) { + /* Octal unsigned integer. */ + base = 8; + goto unsigned_number; + } + else if(p->flags & FLAGS_HEX) { + /* Hexadecimal unsigned integer. */ + + digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; + base = 16; + goto unsigned_number; + } + else if(p->flags & FLAGS_UNSIGNED) { + /* Decimal unsigned integer. */ + base = 10; + goto unsigned_number; + } + + /* Decimal integer. */ + base = 10; + + is_neg = (p->data.num.as_signed < (mp_intmax_t)0) ? 1 : 0; + if(is_neg) { + /* signed_num might fail to hold absolute negative minimum by 1 */ + signed_num = p->data.num.as_signed + (mp_intmax_t)1; + signed_num = -signed_num; + num = (mp_uintmax_t)signed_num; + num += (mp_uintmax_t)1; + } + + goto number; + +unsigned_number: + /* Unsigned number of base BASE. */ + is_neg = 0; + +number: + /* Number of base BASE. */ + + /* Supply a default precision if none was given. */ + if(prec == -1) + prec = 1; + + /* Put the number in WORK. */ + w = workend; + while(num > 0) { + *w-- = digits[num % base]; + num /= base; + } + width -= (long)(workend - w); + prec -= (long)(workend - w); + + if(is_alt && base == 8 && prec <= 0) { + *w-- = '0'; + --width; + } + + if(prec > 0) { + width -= prec; + while(prec-- > 0 && w >= work) + *w-- = '0'; + } + + if(is_alt && base == 16) + width -= 2; + + if(is_neg || (p->flags & FLAGS_SHOWSIGN) || (p->flags & FLAGS_SPACE)) + --width; + + if(!(p->flags & FLAGS_LEFT) && !(p->flags & FLAGS_PAD_NIL)) + while(width-- > 0) + OUTCHAR(' '); + + if(is_neg) + OUTCHAR('-'); + else if(p->flags & FLAGS_SHOWSIGN) + OUTCHAR('+'); + else if(p->flags & FLAGS_SPACE) + OUTCHAR(' '); + + if(is_alt && base == 16) { + OUTCHAR('0'); + if(p->flags & FLAGS_UPPER) + OUTCHAR('X'); + else + OUTCHAR('x'); + } + + if(!(p->flags & FLAGS_LEFT) && (p->flags & FLAGS_PAD_NIL)) + while(width-- > 0) + OUTCHAR('0'); + + /* Write the number. */ + while(++w <= workend) { + OUTCHAR(*w); + } + + if(p->flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + break; + + case FORMAT_STRING: + /* String. */ + { + static const char null[] = "(nil)"; + const char *str; + size_t len; + + str = (char *) p->data.str; + if(!str) { + /* Write null[] if there's space. */ + if(prec == -1 || prec >= (long) sizeof(null) - 1) { + str = null; + len = sizeof(null) - 1; + /* Disable quotes around (nil) */ + p->flags &= (~FLAGS_ALT); + } + else { + str = ""; + len = 0; + } + } + else if(prec != -1) + len = (size_t)prec; + else if(*str == '\0') + len = 0; + else + len = strlen(str); + + width -= (len > LONG_MAX) ? LONG_MAX : (long)len; + + if(p->flags & FLAGS_ALT) + OUTCHAR('"'); + + if(!(p->flags&FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); + + for(; len && *str; len--) + OUTCHAR(*str++); + if(p->flags&FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + + if(p->flags & FLAGS_ALT) + OUTCHAR('"'); + } + break; + + case FORMAT_PTR: + /* Generic pointer. */ + { + void *ptr; + ptr = (void *) p->data.ptr; + if(ptr) { + /* If the pointer is not NULL, write it as a %#x spec. */ + base = 16; + digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; + is_alt = 1; + num = (size_t) ptr; + is_neg = 0; + goto number; + } + else { + /* Write "(nil)" for a nil pointer. */ + static const char strnil[] = "(nil)"; + const char *point; + + width -= (long)(sizeof(strnil) - 1); + if(p->flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + for(point = strnil; *point != '\0'; ++point) + OUTCHAR(*point); + if(!(p->flags & FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); + } + } + break; + + case FORMAT_DOUBLE: + { + char formatbuf[32]="%"; + char *fptr = &formatbuf[1]; + size_t left = sizeof(formatbuf)-strlen(formatbuf); + int len; + + width = -1; + if(p->flags & FLAGS_WIDTH) + width = p->width; + else if(p->flags & FLAGS_WIDTHPARAM) + width = (long)vto[p->width].data.num.as_signed; + + prec = -1; + if(p->flags & FLAGS_PREC) + prec = p->precision; + else if(p->flags & FLAGS_PRECPARAM) + prec = (long)vto[p->precision].data.num.as_signed; + + if(p->flags & FLAGS_LEFT) + *fptr++ = '-'; + if(p->flags & FLAGS_SHOWSIGN) + *fptr++ = '+'; + if(p->flags & FLAGS_SPACE) + *fptr++ = ' '; + if(p->flags & FLAGS_ALT) + *fptr++ = '#'; + + *fptr = 0; + + if(width >= 0) { + if(width >= (long)sizeof(work)) + width = sizeof(work)-1; + /* RECURSIVE USAGE */ + len = curl_msnprintf(fptr, left, "%ld", width); + fptr += len; + left -= len; + } + if(prec >= 0) { + /* for each digit in the integer part, we can have one less + precision */ + size_t maxprec = sizeof(work) - 2; + double val = p->data.dnum; + if(width > 0 && prec <= width) + maxprec -= width; + while(val >= 10.0) { + val /= 10; + maxprec--; + } + + if(prec > (long)maxprec) + prec = (long)maxprec-1; + if(prec < 0) + prec = 0; + /* RECURSIVE USAGE */ + len = curl_msnprintf(fptr, left, ".%ld", prec); + fptr += len; + } + if(p->flags & FLAGS_LONG) + *fptr++ = 'l'; + + if(p->flags & FLAGS_FLOATE) + *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'E':'e'); + else if(p->flags & FLAGS_FLOATG) + *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'G' : 'g'); + else + *fptr++ = 'f'; + + *fptr = 0; /* and a final null-termination */ + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + /* NOTE NOTE NOTE!! Not all sprintf implementations return number of + output characters */ +#ifdef HAVE_SNPRINTF + (snprintf)(work, sizeof(work), formatbuf, p->data.dnum); +#else + (sprintf)(work, formatbuf, p->data.dnum); +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + DEBUGASSERT(strlen(work) <= sizeof(work)); + for(fptr = work; *fptr; fptr++) + OUTCHAR(*fptr); + } + break; + + case FORMAT_INTPTR: + /* Answer the count of characters written. */ +#ifdef HAVE_LONG_LONG_TYPE + if(p->flags & FLAGS_LONGLONG) + *(LONG_LONG_TYPE *) p->data.ptr = (LONG_LONG_TYPE)done; + else +#endif + if(p->flags & FLAGS_LONG) + *(long *) p->data.ptr = (long)done; + else if(!(p->flags & FLAGS_SHORT)) + *(int *) p->data.ptr = (int)done; + else + *(short *) p->data.ptr = (short)done; + break; + + default: + break; + } + f = *end++; /* goto end of %-code */ + + } + return done; +} + +/* fputc() look-alike */ +static int addbyter(int output, FILE *data) +{ + struct nsprintf *infop = (struct nsprintf *)data; + unsigned char outc = (unsigned char)output; + + if(infop->length < infop->max) { + /* only do this if we haven't reached max length yet */ + infop->buffer[0] = outc; /* store */ + infop->buffer++; /* increase pointer */ + infop->length++; /* we are now one byte larger */ + return outc; /* fputc() returns like this on success */ + } + return -1; +} + +int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, + va_list ap_save) +{ + int retcode; + struct nsprintf info; + + info.buffer = buffer; + info.length = 0; + info.max = maxlength; + + retcode = dprintf_formatf(&info, addbyter, format, ap_save); + if(info.max) { + /* we terminate this with a zero byte */ + if(info.max == info.length) { + /* we're at maximum, scrap the last letter */ + info.buffer[-1] = 0; + DEBUGASSERT(retcode); + retcode--; /* don't count the nul byte */ + } + else + info.buffer[0] = 0; + } + return retcode; +} + +int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) +{ + int retcode; + va_list ap_save; /* argument pointer */ + va_start(ap_save, format); + retcode = curl_mvsnprintf(buffer, maxlength, format, ap_save); + va_end(ap_save); + return retcode; +} + +/* fputc() look-alike */ +static int alloc_addbyter(int output, FILE *data) +{ + struct asprintf *infop = (struct asprintf *)data; + unsigned char outc = (unsigned char)output; + + if(Curl_dyn_addn(infop->b, &outc, 1)) { + infop->fail = 1; + return -1; /* fail */ + } + return outc; /* fputc() returns like this on success */ +} + +/* appends the formatted string, returns 0 on success, 1 on error */ +int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save) +{ + struct asprintf info; + info.b = dyn; + info.fail = 0; + + (void)dprintf_formatf(&info, alloc_addbyter, format, ap_save); + if(info.fail) { + Curl_dyn_free(info.b); + return 1; + } + return 0; +} + +char *curl_mvaprintf(const char *format, va_list ap_save) +{ + struct asprintf info; + struct dynbuf dyn; + info.b = &dyn; + Curl_dyn_init(info.b, DYN_APRINTF); + info.fail = 0; + + (void)dprintf_formatf(&info, alloc_addbyter, format, ap_save); + if(info.fail) { + Curl_dyn_free(info.b); + return NULL; + } + if(Curl_dyn_len(info.b)) + return Curl_dyn_ptr(info.b); + return strdup(""); +} + +char *curl_maprintf(const char *format, ...) +{ + va_list ap_save; + char *s; + va_start(ap_save, format); + s = curl_mvaprintf(format, ap_save); + va_end(ap_save); + return s; +} + +static int storebuffer(int output, FILE *data) +{ + char **buffer = (char **)data; + unsigned char outc = (unsigned char)output; + **buffer = outc; + (*buffer)++; + return outc; /* act like fputc() ! */ +} + +int curl_msprintf(char *buffer, const char *format, ...) +{ + va_list ap_save; /* argument pointer */ + int retcode; + va_start(ap_save, format); + retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); + va_end(ap_save); + *buffer = 0; /* we terminate this with a zero byte */ + return retcode; +} + +int curl_mprintf(const char *format, ...) +{ + int retcode; + va_list ap_save; /* argument pointer */ + va_start(ap_save, format); + + retcode = dprintf_formatf(stdout, fputc, format, ap_save); + va_end(ap_save); + return retcode; +} + +int curl_mfprintf(FILE *whereto, const char *format, ...) +{ + int retcode; + va_list ap_save; /* argument pointer */ + va_start(ap_save, format); + retcode = dprintf_formatf(whereto, fputc, format, ap_save); + va_end(ap_save); + return retcode; +} + +int curl_mvsprintf(char *buffer, const char *format, va_list ap_save) +{ + int retcode; + retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); + *buffer = 0; /* we terminate this with a zero byte */ + return retcode; +} + +int curl_mvprintf(const char *format, va_list ap_save) +{ + return dprintf_formatf(stdout, fputc, format, ap_save); +} + +int curl_mvfprintf(FILE *whereto, const char *format, va_list ap_save) +{ + return dprintf_formatf(whereto, fputc, format, ap_save); +} diff --git a/Utilities/cmcurl/lib/mqtt.c b/Utilities/cmcurl/lib/mqtt.c new file mode 100644 index 0000000..366235c --- /dev/null +++ b/Utilities/cmcurl/lib/mqtt.c @@ -0,0 +1,845 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Björn Stenberg, <bjorn@haxx.se> + * + * 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" + +#ifndef CURL_DISABLE_MQTT + +#include "urldata.h" +#include <curl/curl.h> +#include "transfer.h" +#include "sendf.h" +#include "progress.h" +#include "mqtt.h" +#include "select.h" +#include "strdup.h" +#include "url.h" +#include "escape.h" +#include "warnless.h" +#include "curl_printf.h" +#include "curl_memory.h" +#include "multiif.h" +#include "rand.h" + +/* The last #include file should be: */ +#include "memdebug.h" + +#define MQTT_MSG_CONNECT 0x10 +#define MQTT_MSG_CONNACK 0x20 +#define MQTT_MSG_PUBLISH 0x30 +#define MQTT_MSG_SUBSCRIBE 0x82 +#define MQTT_MSG_SUBACK 0x90 +#define MQTT_MSG_DISCONNECT 0xe0 + +#define MQTT_CONNACK_LEN 2 +#define MQTT_SUBACK_LEN 3 +#define MQTT_CLIENTID_LEN 12 /* "curl0123abcd" */ + +/* + * Forward declarations. + */ + +static CURLcode mqtt_do(struct Curl_easy *data, bool *done); +static CURLcode mqtt_done(struct Curl_easy *data, + CURLcode status, bool premature); +static CURLcode mqtt_doing(struct Curl_easy *data, bool *done); +static int mqtt_getsock(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *sock); +static CURLcode mqtt_setup_conn(struct Curl_easy *data, + struct connectdata *conn); + +/* + * MQTT protocol handler. + */ + +const struct Curl_handler Curl_handler_mqtt = { + "MQTT", /* scheme */ + mqtt_setup_conn, /* setup_connection */ + mqtt_do, /* do_it */ + mqtt_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + mqtt_doing, /* doing */ + ZERO_NULL, /* proto_getsock */ + mqtt_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_MQTT, /* defport */ + CURLPROTO_MQTT, /* protocol */ + CURLPROTO_MQTT, /* family */ + PROTOPT_NONE /* flags */ +}; + +static CURLcode mqtt_setup_conn(struct Curl_easy *data, + struct connectdata *conn) +{ + /* allocate the HTTP-specific struct for the Curl_easy, only to survive + during this request */ + struct MQTT *mq; + (void)conn; + DEBUGASSERT(data->req.p.mqtt == NULL); + + mq = calloc(1, sizeof(struct MQTT)); + if(!mq) + return CURLE_OUT_OF_MEMORY; + Curl_dyn_init(&mq->recvbuf, DYN_MQTT_RECV); + data->req.p.mqtt = mq; + return CURLE_OK; +} + +static CURLcode mqtt_send(struct Curl_easy *data, + char *buf, size_t len) +{ + CURLcode result = CURLE_OK; + struct MQTT *mq = data->req.p.mqtt; + ssize_t n; + result = Curl_nwrite(data, FIRSTSOCKET, buf, len, &n); + if(result) + return result; + Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n); + if(len != (size_t)n) { + size_t nsend = len - n; + char *sendleftovers = Curl_memdup(&buf[n], nsend); + if(!sendleftovers) + return CURLE_OUT_OF_MEMORY; + mq->sendleftovers = sendleftovers; + mq->nsend = nsend; + } + else { + mq->sendleftovers = NULL; + mq->nsend = 0; + } + return result; +} + +/* Generic function called by the multi interface to figure out what socket(s) + to wait for and for what actions during the DOING and PROTOCONNECT + states */ +static int mqtt_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *sock) +{ + (void)data; + sock[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_READSOCK(FIRSTSOCKET); +} + +static int mqtt_encode_len(char *buf, size_t len) +{ + unsigned char encoded; + int i; + + for(i = 0; (len > 0) && (i<4); i++) { + encoded = len % 0x80; + len /= 0x80; + if(len) + encoded |= 0x80; + buf[i] = encoded; + } + + return i; +} + +/* add the passwd to the CONNECT packet */ +static int add_passwd(const char *passwd, const size_t plen, + char *pkt, const size_t start, int remain_pos) +{ + /* magic number that need to be set properly */ + const size_t conn_flags_pos = remain_pos + 8; + if(plen > 0xffff) + return 1; + + /* set password flag */ + pkt[conn_flags_pos] |= 0x40; + + /* length of password provided */ + pkt[start] = (char)((plen >> 8) & 0xFF); + pkt[start + 1] = (char)(plen & 0xFF); + memcpy(&pkt[start + 2], passwd, plen); + return 0; +} + +/* add user to the CONNECT packet */ +static int add_user(const char *username, const size_t ulen, + unsigned char *pkt, const size_t start, int remain_pos) +{ + /* magic number that need to be set properly */ + const size_t conn_flags_pos = remain_pos + 8; + if(ulen > 0xffff) + return 1; + + /* set username flag */ + pkt[conn_flags_pos] |= 0x80; + /* length of username provided */ + pkt[start] = (unsigned char)((ulen >> 8) & 0xFF); + pkt[start + 1] = (unsigned char)(ulen & 0xFF); + memcpy(&pkt[start + 2], username, ulen); + return 0; +} + +/* add client ID to the CONNECT packet */ +static int add_client_id(const char *client_id, const size_t client_id_len, + char *pkt, const size_t start) +{ + if(client_id_len != MQTT_CLIENTID_LEN) + return 1; + pkt[start] = 0x00; + pkt[start + 1] = MQTT_CLIENTID_LEN; + memcpy(&pkt[start + 2], client_id, MQTT_CLIENTID_LEN); + return 0; +} + +/* Set initial values of CONNECT packet */ +static int init_connpack(char *packet, char *remain, int remain_pos) +{ + /* Fixed header starts */ + /* packet type */ + packet[0] = MQTT_MSG_CONNECT; + /* remaining length field */ + memcpy(&packet[1], remain, remain_pos); + /* Fixed header ends */ + + /* Variable header starts */ + /* protocol length */ + packet[remain_pos + 1] = 0x00; + packet[remain_pos + 2] = 0x04; + /* protocol name */ + packet[remain_pos + 3] = 'M'; + packet[remain_pos + 4] = 'Q'; + packet[remain_pos + 5] = 'T'; + packet[remain_pos + 6] = 'T'; + /* protocol level */ + packet[remain_pos + 7] = 0x04; + /* CONNECT flag: CleanSession */ + packet[remain_pos + 8] = 0x02; + /* keep-alive 0 = disabled */ + packet[remain_pos + 9] = 0x00; + packet[remain_pos + 10] = 0x3c; + /* end of variable header */ + return remain_pos + 10; +} + +static CURLcode mqtt_connect(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + int pos = 0; + int rc = 0; + /* remain length */ + int remain_pos = 0; + char remain[4] = {0}; + size_t packetlen = 0; + size_t payloadlen = 0; + size_t start_user = 0; + size_t start_pwd = 0; + char client_id[MQTT_CLIENTID_LEN + 1] = "curl"; + const size_t clen = strlen("curl"); + char *packet = NULL; + + /* extracting username from request */ + const char *username = data->state.aptr.user ? + data->state.aptr.user : ""; + const size_t ulen = strlen(username); + /* extracting password from request */ + const char *passwd = data->state.aptr.passwd ? + data->state.aptr.passwd : ""; + const size_t plen = strlen(passwd); + + payloadlen = ulen + plen + MQTT_CLIENTID_LEN + 2; + /* The plus 2 are for the MSB and LSB describing the length of the string to + * be added on the payload. Refer to spec 1.5.2 and 1.5.4 */ + if(ulen) + payloadlen += 2; + if(plen) + payloadlen += 2; + + /* getting how much occupy the remain length */ + remain_pos = mqtt_encode_len(remain, payloadlen + 10); + + /* 10 length of variable header and 1 the first byte of the fixed header */ + packetlen = payloadlen + 10 + remain_pos + 1; + + /* allocating packet */ + if(packetlen > 268435455) + return CURLE_WEIRD_SERVER_REPLY; + packet = malloc(packetlen); + if(!packet) + return CURLE_OUT_OF_MEMORY; + memset(packet, 0, packetlen); + + /* set initial values for the CONNECT packet */ + pos = init_connpack(packet, remain, remain_pos); + + result = Curl_rand_alnum(data, (unsigned char *)&client_id[clen], + MQTT_CLIENTID_LEN - clen + 1); + /* add client id */ + rc = add_client_id(client_id, strlen(client_id), packet, pos + 1); + if(rc) { + failf(data, "Client ID length mismatched: [%zu]", strlen(client_id)); + result = CURLE_WEIRD_SERVER_REPLY; + goto end; + } + infof(data, "Using client id '%s'", client_id); + + /* position where starts the user payload */ + start_user = pos + 3 + MQTT_CLIENTID_LEN; + /* position where starts the password payload */ + start_pwd = start_user + ulen; + /* if user name was provided, add it to the packet */ + if(ulen) { + start_pwd += 2; + + rc = add_user(username, ulen, + (unsigned char *)packet, start_user, remain_pos); + if(rc) { + failf(data, "Username is too large: [%zu]", ulen); + result = CURLE_WEIRD_SERVER_REPLY; + goto end; + } + } + + /* if passwd was provided, add it to the packet */ + if(plen) { + rc = add_passwd(passwd, plen, packet, start_pwd, remain_pos); + if(rc) { + failf(data, "Password is too large: [%zu]", plen); + result = CURLE_WEIRD_SERVER_REPLY; + goto end; + } + } + + if(!result) + result = mqtt_send(data, packet, packetlen); + +end: + if(packet) + free(packet); + Curl_safefree(data->state.aptr.user); + Curl_safefree(data->state.aptr.passwd); + return result; +} + +static CURLcode mqtt_disconnect(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct MQTT *mq = data->req.p.mqtt; + result = mqtt_send(data, (char *)"\xe0\x00", 2); + Curl_safefree(mq->sendleftovers); + Curl_dyn_free(&mq->recvbuf); + return result; +} + +static CURLcode mqtt_recv_atleast(struct Curl_easy *data, size_t nbytes) +{ + struct MQTT *mq = data->req.p.mqtt; + size_t rlen = Curl_dyn_len(&mq->recvbuf); + CURLcode result; + + if(rlen < nbytes) { + unsigned char readbuf[1024]; + ssize_t nread; + + DEBUGASSERT(nbytes - rlen < sizeof(readbuf)); + result = Curl_read(data, data->conn->sock[FIRSTSOCKET], + (char *)readbuf, nbytes - rlen, &nread); + if(result) + return result; + DEBUGASSERT(nread >= 0); + if(Curl_dyn_addn(&mq->recvbuf, readbuf, (size_t)nread)) + return CURLE_OUT_OF_MEMORY; + rlen = Curl_dyn_len(&mq->recvbuf); + } + return (rlen >= nbytes)? CURLE_OK : CURLE_AGAIN; +} + +static void mqtt_recv_consume(struct Curl_easy *data, size_t nbytes) +{ + struct MQTT *mq = data->req.p.mqtt; + size_t rlen = Curl_dyn_len(&mq->recvbuf); + if(rlen <= nbytes) + Curl_dyn_reset(&mq->recvbuf); + else + Curl_dyn_tail(&mq->recvbuf, rlen - nbytes); +} + +static CURLcode mqtt_verify_connack(struct Curl_easy *data) +{ + struct MQTT *mq = data->req.p.mqtt; + CURLcode result; + char *ptr; + + result = mqtt_recv_atleast(data, MQTT_CONNACK_LEN); + if(result) + goto fail; + + /* verify CONNACK */ + DEBUGASSERT(Curl_dyn_len(&mq->recvbuf) >= MQTT_CONNACK_LEN); + ptr = Curl_dyn_ptr(&mq->recvbuf); + Curl_debug(data, CURLINFO_HEADER_IN, ptr, MQTT_CONNACK_LEN); + + if(ptr[0] != 0x00 || ptr[1] != 0x00) { + failf(data, "Expected %02x%02x but got %02x%02x", + 0x00, 0x00, ptr[0], ptr[1]); + Curl_dyn_reset(&mq->recvbuf); + result = CURLE_WEIRD_SERVER_REPLY; + goto fail; + } + mqtt_recv_consume(data, MQTT_CONNACK_LEN); +fail: + return result; +} + +static CURLcode mqtt_get_topic(struct Curl_easy *data, + char **topic, size_t *topiclen) +{ + 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); + if(!result && (*topiclen > 0xffff)) { + failf(data, "Too long MQTT topic"); + result = CURLE_URL_MALFORMAT; + } + } + else + failf(data, "No MQTT topic found. Forgot to URL encode it?"); + + return result; +} + +static CURLcode mqtt_subscribe(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + char *topic = NULL; + size_t topiclen; + unsigned char *packet = NULL; + size_t packetlen; + char encodedsize[4]; + size_t n; + struct connectdata *conn = data->conn; + + result = mqtt_get_topic(data, &topic, &topiclen); + if(result) + goto fail; + + conn->proto.mqtt.packetid++; + + packetlen = topiclen + 5; /* packetid + topic (has a two byte length field) + + 2 bytes topic length + QoS byte */ + n = mqtt_encode_len((char *)encodedsize, packetlen); + packetlen += n + 1; /* add one for the control packet type byte */ + + packet = malloc(packetlen); + if(!packet) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + + packet[0] = MQTT_MSG_SUBSCRIBE; + memcpy(&packet[1], encodedsize, n); + packet[1 + n] = (conn->proto.mqtt.packetid >> 8) & 0xff; + packet[2 + n] = conn->proto.mqtt.packetid & 0xff; + packet[3 + n] = (topiclen >> 8) & 0xff; + packet[4 + n ] = topiclen & 0xff; + memcpy(&packet[5 + n], topic, topiclen); + packet[5 + n + topiclen] = 0; /* QoS zero */ + + result = mqtt_send(data, (char *)packet, packetlen); + +fail: + free(topic); + free(packet); + return result; +} + +/* + * Called when the first byte was already read. + */ +static CURLcode mqtt_verify_suback(struct Curl_easy *data) +{ + struct MQTT *mq = data->req.p.mqtt; + struct connectdata *conn = data->conn; + struct mqtt_conn *mqtt = &conn->proto.mqtt; + CURLcode result; + char *ptr; + + result = mqtt_recv_atleast(data, MQTT_SUBACK_LEN); + if(result) + goto fail; + + /* verify SUBACK */ + DEBUGASSERT(Curl_dyn_len(&mq->recvbuf) >= MQTT_SUBACK_LEN); + ptr = Curl_dyn_ptr(&mq->recvbuf); + Curl_debug(data, CURLINFO_HEADER_IN, ptr, MQTT_SUBACK_LEN); + + if(((unsigned char)ptr[0]) != ((mqtt->packetid >> 8) & 0xff) || + ((unsigned char)ptr[1]) != (mqtt->packetid & 0xff) || + ptr[2] != 0x00) { + Curl_dyn_reset(&mq->recvbuf); + result = CURLE_WEIRD_SERVER_REPLY; + goto fail; + } + mqtt_recv_consume(data, MQTT_SUBACK_LEN); +fail: + return result; +} + +static CURLcode mqtt_publish(struct Curl_easy *data) +{ + CURLcode result; + char *payload = data->set.postfields; + size_t payloadlen; + char *topic = NULL; + size_t topiclen; + unsigned char *pkt = NULL; + size_t i = 0; + size_t remaininglength; + size_t encodelen; + char encodedbytes[4]; + curl_off_t postfieldsize = data->set.postfieldsize; + + if(!payload) + return CURLE_BAD_FUNCTION_ARGUMENT; + if(postfieldsize < 0) + payloadlen = strlen(payload); + else + payloadlen = (size_t)postfieldsize; + + result = mqtt_get_topic(data, &topic, &topiclen); + if(result) + goto fail; + + remaininglength = payloadlen + 2 + topiclen; + encodelen = mqtt_encode_len(encodedbytes, remaininglength); + + /* add the control byte and the encoded remaining length */ + pkt = malloc(remaininglength + 1 + encodelen); + if(!pkt) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + + /* assemble packet */ + pkt[i++] = MQTT_MSG_PUBLISH; + memcpy(&pkt[i], encodedbytes, encodelen); + i += encodelen; + pkt[i++] = (topiclen >> 8) & 0xff; + pkt[i++] = (topiclen & 0xff); + memcpy(&pkt[i], topic, topiclen); + i += topiclen; + memcpy(&pkt[i], payload, payloadlen); + i += payloadlen; + result = mqtt_send(data, (char *)pkt, i); + +fail: + free(pkt); + free(topic); + return result; +} + +static size_t mqtt_decode_len(unsigned char *buf, + size_t buflen, size_t *lenbytes) +{ + size_t len = 0; + size_t mult = 1; + size_t i; + unsigned char encoded = 128; + + for(i = 0; (i < buflen) && (encoded & 128); i++) { + encoded = buf[i]; + len += (encoded & 127) * mult; + mult *= 128; + } + + if(lenbytes) + *lenbytes = i; + + return len; +} + +#ifdef CURLDEBUG +static const char *statenames[]={ + "MQTT_FIRST", + "MQTT_REMAINING_LENGTH", + "MQTT_CONNACK", + "MQTT_SUBACK", + "MQTT_SUBACK_COMING", + "MQTT_PUBWAIT", + "MQTT_PUB_REMAIN", + + "NOT A STATE" +}; +#endif + +/* The only way to change state */ +static void mqstate(struct Curl_easy *data, + enum mqttstate state, + enum mqttstate nextstate) /* used if state == FIRST */ +{ + struct connectdata *conn = data->conn; + struct mqtt_conn *mqtt = &conn->proto.mqtt; +#ifdef CURLDEBUG + infof(data, "%s (from %s) (next is %s)", + statenames[state], + statenames[mqtt->state], + (state == MQTT_FIRST)? statenames[nextstate] : ""); +#endif + mqtt->state = state; + if(state == MQTT_FIRST) + mqtt->nextstate = nextstate; +} + + +static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + ssize_t nread; + unsigned char *pkt = (unsigned char *)data->state.buffer; + size_t remlen; + struct mqtt_conn *mqtt = &conn->proto.mqtt; + struct MQTT *mq = data->req.p.mqtt; + unsigned char packet; + + switch(mqtt->state) { +MQTT_SUBACK_COMING: + case MQTT_SUBACK_COMING: + result = mqtt_verify_suback(data); + if(result) + break; + + mqstate(data, MQTT_FIRST, MQTT_PUBWAIT); + break; + + case MQTT_SUBACK: + case MQTT_PUBWAIT: + /* we are expecting PUBLISH or SUBACK */ + packet = mq->firstbyte & 0xf0; + if(packet == MQTT_MSG_PUBLISH) + mqstate(data, MQTT_PUB_REMAIN, MQTT_NOSTATE); + else if(packet == MQTT_MSG_SUBACK) { + mqstate(data, MQTT_SUBACK_COMING, MQTT_NOSTATE); + goto MQTT_SUBACK_COMING; + } + else if(packet == MQTT_MSG_DISCONNECT) { + infof(data, "Got DISCONNECT"); + *done = TRUE; + goto end; + } + else { + result = CURLE_WEIRD_SERVER_REPLY; + goto end; + } + + /* -- switched state -- */ + remlen = mq->remaining_length; + infof(data, "Remaining length: %zu bytes", remlen); + if(data->set.max_filesize && + (curl_off_t)remlen > data->set.max_filesize) { + failf(data, "Maximum file size exceeded"); + result = CURLE_FILESIZE_EXCEEDED; + goto end; + } + Curl_pgrsSetDownloadSize(data, remlen); + data->req.bytecount = 0; + data->req.size = remlen; + mq->npacket = remlen; /* get this many bytes */ + /* FALLTHROUGH */ + case MQTT_PUB_REMAIN: { + /* read rest of packet, but no more. Cap to buffer size */ + size_t rest = mq->npacket; + if(rest > (size_t)data->set.buffer_size) + rest = (size_t)data->set.buffer_size; + result = Curl_read(data, sockfd, (char *)pkt, rest, &nread); + if(result) { + if(CURLE_AGAIN == result) { + infof(data, "EEEE AAAAGAIN"); + } + goto end; + } + if(!nread) { + infof(data, "server disconnected"); + result = CURLE_PARTIAL_FILE; + goto end; + } + + mq->npacket -= nread; + + /* if QoS is set, message contains packet id */ + + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)pkt, nread); + if(result) + goto end; + + if(!mq->npacket) + /* no more PUBLISH payload, back to subscribe wait state */ + mqstate(data, MQTT_FIRST, MQTT_PUBWAIT); + break; + } + default: + DEBUGASSERT(NULL); /* illegal state */ + result = CURLE_WEIRD_SERVER_REPLY; + goto end; + } +end: + return result; +} + +static CURLcode mqtt_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + *done = FALSE; /* unconditionally */ + + result = mqtt_connect(data); + if(result) { + failf(data, "Error %d sending MQTT CONNECT request", result); + return result; + } + mqstate(data, MQTT_FIRST, MQTT_CONNACK); + return CURLE_OK; +} + +static CURLcode mqtt_done(struct Curl_easy *data, + CURLcode status, bool premature) +{ + struct MQTT *mq = data->req.p.mqtt; + (void)status; + (void)premature; + Curl_safefree(mq->sendleftovers); + Curl_dyn_free(&mq->recvbuf); + return CURLE_OK; +} + +static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct mqtt_conn *mqtt = &conn->proto.mqtt; + struct MQTT *mq = data->req.p.mqtt; + ssize_t nread; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + unsigned char *pkt = (unsigned char *)data->state.buffer; + unsigned char byte; + + *done = FALSE; + + if(mq->nsend) { + /* send the remainder of an outgoing packet */ + char *ptr = mq->sendleftovers; + result = mqtt_send(data, mq->sendleftovers, mq->nsend); + free(ptr); + if(result) + return result; + } + + infof(data, "mqtt_doing: state [%d]", (int) mqtt->state); + switch(mqtt->state) { + case MQTT_FIRST: + /* Read the initial byte only */ + result = Curl_read(data, sockfd, (char *)&mq->firstbyte, 1, &nread); + if(result) + break; + else if(!nread) { + failf(data, "Connection disconnected"); + *done = TRUE; + result = CURLE_RECV_ERROR; + break; + } + Curl_debug(data, CURLINFO_HEADER_IN, (char *)&mq->firstbyte, 1); + /* remember the first byte */ + mq->npacket = 0; + mqstate(data, MQTT_REMAINING_LENGTH, MQTT_NOSTATE); + /* FALLTHROUGH */ + case MQTT_REMAINING_LENGTH: + do { + result = Curl_read(data, sockfd, (char *)&byte, 1, &nread); + if(!nread) + break; + Curl_debug(data, CURLINFO_HEADER_IN, (char *)&byte, 1); + pkt[mq->npacket++] = byte; + } while((byte & 0x80) && (mq->npacket < 4)); + if(nread && (byte & 0x80)) + /* MQTT supports up to 127 * 128^0 + 127 * 128^1 + 127 * 128^2 + + 127 * 128^3 bytes. server tried to send more */ + result = CURLE_WEIRD_SERVER_REPLY; + if(result) + break; + mq->remaining_length = mqtt_decode_len(&pkt[0], mq->npacket, NULL); + mq->npacket = 0; + if(mq->remaining_length) { + mqstate(data, mqtt->nextstate, MQTT_NOSTATE); + break; + } + mqstate(data, MQTT_FIRST, MQTT_FIRST); + + if(mq->firstbyte == MQTT_MSG_DISCONNECT) { + infof(data, "Got DISCONNECT"); + *done = TRUE; + } + break; + case MQTT_CONNACK: + result = mqtt_verify_connack(data); + if(result) + break; + + if(data->state.httpreq == HTTPREQ_POST) { + result = mqtt_publish(data); + if(!result) { + result = mqtt_disconnect(data); + *done = TRUE; + } + mqtt->nextstate = MQTT_FIRST; + } + else { + result = mqtt_subscribe(data); + if(!result) { + mqstate(data, MQTT_FIRST, MQTT_SUBACK); + } + } + break; + + case MQTT_SUBACK: + case MQTT_PUBWAIT: + case MQTT_PUB_REMAIN: + result = mqtt_read_publish(data, done); + break; + + default: + failf(data, "State not handled yet"); + *done = TRUE; + break; + } + + if(result == CURLE_AGAIN) + result = CURLE_OK; + return result; +} + +#endif /* CURL_DISABLE_MQTT */ diff --git a/Utilities/cmcurl/lib/mqtt.h b/Utilities/cmcurl/lib/mqtt.h new file mode 100644 index 0000000..84f1770 --- /dev/null +++ b/Utilities/cmcurl/lib/mqtt.h @@ -0,0 +1,62 @@ +#ifndef HEADER_CURL_MQTT_H +#define HEADER_CURL_MQTT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Björn Stenberg, <bjorn@haxx.se> + * + * 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 + * + ***************************************************************************/ + +#ifndef CURL_DISABLE_MQTT +extern const struct Curl_handler Curl_handler_mqtt; +#endif + +enum mqttstate { + MQTT_FIRST, /* 0 */ + MQTT_REMAINING_LENGTH, /* 1 */ + MQTT_CONNACK, /* 2 */ + MQTT_SUBACK, /* 3 */ + MQTT_SUBACK_COMING, /* 4 - the SUBACK remainder */ + MQTT_PUBWAIT, /* 5 - wait for publish */ + MQTT_PUB_REMAIN, /* 6 - wait for the remainder of the publish */ + + MQTT_NOSTATE /* 7 - never used an actual state */ +}; + +struct mqtt_conn { + enum mqttstate state; + enum mqttstate nextstate; /* switch to this after remaining length is + done */ + unsigned int packetid; +}; + +/* protocol-specific transfer-related data */ +struct MQTT { + char *sendleftovers; + size_t nsend; /* size of sendleftovers */ + + /* when receiving */ + size_t npacket; /* byte counter */ + unsigned char firstbyte; + size_t remaining_length; + struct dynbuf recvbuf; +}; + +#endif /* HEADER_CURL_MQTT_H */ diff --git a/Utilities/cmcurl/lib/multi.c b/Utilities/cmcurl/lib/multi.c new file mode 100644 index 0000000..5456113 --- /dev/null +++ b/Utilities/cmcurl/lib/multi.c @@ -0,0 +1,3743 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "urldata.h" +#include "transfer.h" +#include "url.h" +#include "cfilters.h" +#include "connect.h" +#include "progress.h" +#include "easyif.h" +#include "share.h" +#include "psl.h" +#include "multiif.h" +#include "sendf.h" +#include "timeval.h" +#include "http.h" +#include "select.h" +#include "warnless.h" +#include "speedcheck.h" +#include "conncache.h" +#include "multihandle.h" +#include "sigpipe.h" +#include "vtls/vtls.h" +#include "http_proxy.h" +#include "http2.h" +#include "socketpair.h" +#include "socks.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* + 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 45-50 K memory, therefore this 3K are not significant. +*/ +#ifndef CURL_SOCKET_HASH_TABLE_SIZE +#define CURL_SOCKET_HASH_TABLE_SIZE 911 +#endif + +#ifndef CURL_CONNECTION_HASH_SIZE +#define CURL_CONNECTION_HASH_SIZE 97 +#endif + +#ifndef CURL_DNS_HASH_SIZE +#define CURL_DNS_HASH_SIZE 71 +#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 CURLMcode singlesocket(struct Curl_multi *multi, + struct Curl_easy *data); +static CURLMcode add_next_timeout(struct curltime now, + struct Curl_multi *multi, + struct Curl_easy *d); +static CURLMcode multi_timeout(struct Curl_multi *multi, + long *timeout_ms); +static void process_pending_handles(struct Curl_multi *multi); + +#ifdef DEBUGBUILD +static const char * const multi_statename[]={ + "INIT", + "PENDING", + "CONNECT", + "RESOLVING", + "CONNECTING", + "TUNNELING", + "PROTOCONNECT", + "PROTOCONNECTING", + "DO", + "DOING", + "DOING_MORE", + "DID", + "PERFORMING", + "RATELIMITING", + "DONE", + "COMPLETED", + "MSGSENT", +}; +#endif + +/* function pointer called once when switching TO a state */ +typedef void (*init_multistate_func)(struct Curl_easy *data); + +/* called in DID state, before PERFORMING state */ +static void before_perform(struct Curl_easy *data) +{ + data->req.chunk = FALSE; + Curl_pgrsTime(data, TIMER_PRETRANSFER); +} + +static void init_completed(struct Curl_easy *data) +{ + /* this is a completed transfer */ + + /* Important: reset the conn pointer so that we don't point to memory + that could be freed anytime */ + Curl_detach_connection(data); + Curl_expire_clear(data); /* stop all timers */ +} + +/* always use this function to change state, to make debugging easier */ +static void mstate(struct Curl_easy *data, CURLMstate state +#ifdef DEBUGBUILD + , int lineno +#endif +) +{ + CURLMstate oldstate = data->mstate; + static const init_multistate_func finit[MSTATE_LAST] = { + NULL, /* INIT */ + NULL, /* PENDING */ + Curl_init_CONNECT, /* CONNECT */ + NULL, /* RESOLVING */ + NULL, /* CONNECTING */ + NULL, /* TUNNELING */ + NULL, /* PROTOCONNECT */ + NULL, /* PROTOCONNECTING */ + NULL, /* DO */ + NULL, /* DOING */ + NULL, /* DOING_MORE */ + before_perform, /* DID */ + NULL, /* PERFORMING */ + NULL, /* RATELIMITING */ + NULL, /* DONE */ + init_completed, /* COMPLETED */ + NULL /* MSGSENT */ + }; + +#if defined(DEBUGBUILD) && defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) lineno; +#endif + + if(oldstate == state) + /* don't bother when the new state is the same as the old state */ + return; + + data->mstate = state; + +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + if(data->mstate >= MSTATE_PENDING && + data->mstate < MSTATE_COMPLETED) { + infof(data, + "STATE: %s => %s handle %p; line %d", + multi_statename[oldstate], multi_statename[data->mstate], + (void *)data, lineno); + } +#endif + + if(state == MSTATE_COMPLETED) { + /* changing to COMPLETED means there's one less easy handle 'alive' */ + DEBUGASSERT(data->multi->num_alive > 0); + data->multi->num_alive--; + } + + /* if this state has an init-function, run it */ + if(finit[state]) + finit[state](data); +} + +#ifndef DEBUGBUILD +#define multistate(x,y) mstate(x,y) +#else +#define multistate(x,y) mstate(x,y, __LINE__) +#endif + +/* + * We add one of these structs to the sockhash for each socket + */ + +struct Curl_sh_entry { + struct Curl_hash transfers; /* hash of transfers using this socket */ + unsigned int action; /* what combined action READ/WRITE this socket waits + for */ + unsigned int users; /* number of transfers using this */ + void *socketp; /* settable by users with curl_multi_assign() */ + unsigned int readers; /* this many transfers want to read */ + unsigned int writers; /* this many transfers want to write */ +}; + +/* look up a given socket in the socket hash, skip invalid sockets */ +static struct Curl_sh_entry *sh_getentry(struct Curl_hash *sh, + curl_socket_t s) +{ + if(s != CURL_SOCKET_BAD) { + /* only look for proper sockets */ + return Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); + } + return NULL; +} + +#define TRHASH_SIZE 13 +static size_t trhash(void *key, size_t key_length, size_t slots_num) +{ + size_t keyval = (size_t)*(struct Curl_easy **)key; + (void) key_length; + + return (keyval % slots_num); +} + +static size_t trhash_compare(void *k1, size_t k1_len, void *k2, size_t k2_len) +{ + (void)k1_len; + (void)k2_len; + + return *(struct Curl_easy **)k1 == *(struct Curl_easy **)k2; +} + +static void trhash_dtor(void *nada) +{ + (void)nada; +} + +/* + * The sockhash has its own separate subhash in each entry that need to be + * safely destroyed first. + */ +static void sockhash_destroy(struct Curl_hash *h) +{ + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + + DEBUGASSERT(h); + Curl_hash_start_iterate(h, &iter); + he = Curl_hash_next_element(&iter); + while(he) { + struct Curl_sh_entry *sh = (struct Curl_sh_entry *)he->ptr; + Curl_hash_destroy(&sh->transfers); + he = Curl_hash_next_element(&iter); + } + Curl_hash_destroy(h); +} + + +/* make sure this socket is present in the hash for this handle */ +static struct Curl_sh_entry *sh_addentry(struct Curl_hash *sh, + curl_socket_t s) +{ + struct Curl_sh_entry *there = sh_getentry(sh, s); + struct Curl_sh_entry *check; + + if(there) { + /* it is present, return fine */ + return there; + } + + /* not present, add it */ + check = calloc(1, sizeof(struct Curl_sh_entry)); + if(!check) + return NULL; /* major failure */ + + Curl_hash_init(&check->transfers, TRHASH_SIZE, trhash, trhash_compare, + trhash_dtor); + + /* make/add new hash entry */ + if(!Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) { + Curl_hash_destroy(&check->transfers); + free(check); + return NULL; /* major failure */ + } + + return check; /* things are good in sockhash land */ +} + + +/* delete the given socket + handle from the hash */ +static void sh_delentry(struct Curl_sh_entry *entry, + struct Curl_hash *sh, curl_socket_t s) +{ + Curl_hash_destroy(&entry->transfers); + + /* We remove the hash entry. This will end up in a call to + sh_freeentry(). */ + Curl_hash_delete(sh, (char *)&s, sizeof(curl_socket_t)); +} + +/* + * free a sockhash entry + */ +static void sh_freeentry(void *freethis) +{ + struct Curl_sh_entry *p = (struct Curl_sh_entry *) freethis; + + free(p); +} + +static size_t fd_key_compare(void *k1, size_t k1_len, void *k2, size_t k2_len) +{ + (void) k1_len; (void) k2_len; + + return (*((curl_socket_t *) k1)) == (*((curl_socket_t *) k2)); +} + +static size_t hash_fd(void *key, size_t key_length, size_t slots_num) +{ + curl_socket_t fd = *((curl_socket_t *) key); + (void) key_length; + + return (fd % slots_num); +} + +/* + * sh_init() creates a new socket hash and returns the handle for it. + * + * Quote from README.multi_socket: + * + * "Some tests at 7000 and 9000 connections showed that the socket hash lookup + * is somewhat of a bottle neck. Its current implementation may be a bit too + * limiting. It simply has a fixed-size array, and on each entry in the array + * it has a linked list with entries. So the hash only checks which list to + * scan through. The code I had used so for used a list with merely 7 slots + * (as that is what the DNS hash uses) but with 7000 connections that would + * make an average of 1000 nodes in each list to run through. I upped that to + * 97 slots (I believe a prime is suitable) and noticed a significant speed + * increase. I need to reconsider the hash implementation or use a rather + * large default value like this. At 9000 connections I was still below 10us + * per call." + * + */ +static void sh_init(struct Curl_hash *hash, int hashsize) +{ + Curl_hash_init(hash, hashsize, hash_fd, fd_key_compare, + sh_freeentry); +} + +/* + * multi_addmsg() + * + * Called when a transfer is completed. Adds the given msg pointer to + * the list kept in the multi handle. + */ +static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg) +{ + Curl_llist_insert_next(&multi->msglist, multi->msglist.tail, msg, + &msg->list); +} + +struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ + int chashsize, /* connection hash */ + int dnssize) /* dns hash */ +{ + struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi)); + + if(!multi) + return NULL; + + multi->magic = CURL_MULTI_HANDLE; + + Curl_init_dnscache(&multi->hostcache, dnssize); + + sh_init(&multi->sockhash, hashsize); + + if(Curl_conncache_init(&multi->conn_cache, chashsize)) + goto error; + + Curl_llist_init(&multi->msglist, NULL); + Curl_llist_init(&multi->pending, NULL); + Curl_llist_init(&multi->msgsent, NULL); + + multi->multiplexing = TRUE; + multi->max_concurrent_streams = 100; + +#ifdef USE_WINSOCK + multi->wsa_event = WSACreateEvent(); + if(multi->wsa_event == WSA_INVALID_EVENT) + goto error; +#else +#ifdef ENABLE_WAKEUP + if(wakeup_create(multi->wakeup_pair) < 0) { + multi->wakeup_pair[0] = CURL_SOCKET_BAD; + multi->wakeup_pair[1] = CURL_SOCKET_BAD; + } + else if(curlx_nonblock(multi->wakeup_pair[0], TRUE) < 0 || + curlx_nonblock(multi->wakeup_pair[1], TRUE) < 0) { + wakeup_close(multi->wakeup_pair[0]); + wakeup_close(multi->wakeup_pair[1]); + multi->wakeup_pair[0] = CURL_SOCKET_BAD; + multi->wakeup_pair[1] = CURL_SOCKET_BAD; + } +#endif +#endif + + return multi; + +error: + + sockhash_destroy(&multi->sockhash); + Curl_hash_destroy(&multi->hostcache); + Curl_conncache_destroy(&multi->conn_cache); + free(multi); + return NULL; +} + +struct Curl_multi *curl_multi_init(void) +{ + return Curl_multi_handle(CURL_SOCKET_HASH_TABLE_SIZE, + CURL_CONNECTION_HASH_SIZE, + CURL_DNS_HASH_SIZE); +} + +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) +static void multi_warn_debug(struct Curl_multi *multi, struct Curl_easy *data) +{ + if(!multi->warned) { + infof(data, "!!! WARNING !!!"); + infof(data, "This is a debug build of libcurl, " + "do not use in production."); + multi->warned = true; + } +} +#else +#define multi_warn_debug(x,y) Curl_nop_stmt +#endif + +/* returns TRUE if the easy handle is supposed to be present in the main link + list */ +static bool in_main_list(struct Curl_easy *data) +{ + return ((data->mstate != MSTATE_PENDING) && + (data->mstate != MSTATE_MSGSENT)); +} + +static void link_easy(struct Curl_multi *multi, + struct Curl_easy *data) +{ + /* We add the new easy entry last in the list. */ + data->next = NULL; /* end of the line */ + if(multi->easyp) { + struct Curl_easy *last = multi->easylp; + last->next = data; + data->prev = last; + multi->easylp = data; /* the new last node */ + } + else { + /* first node, make prev NULL! */ + data->prev = NULL; + multi->easylp = multi->easyp = data; /* both first and last */ + } +} + +/* unlink the given easy handle from the linked list of easy handles */ +static void unlink_easy(struct Curl_multi *multi, + struct Curl_easy *data) +{ + /* make the previous node point to our next */ + if(data->prev) + data->prev->next = data->next; + else + multi->easyp = data->next; /* point to first node */ + + /* make our next point to our previous node */ + if(data->next) + data->next->prev = data->prev; + else + multi->easylp = data->prev; /* point to last node */ + + data->prev = data->next = NULL; +} + + +CURLMcode curl_multi_add_handle(struct Curl_multi *multi, + struct Curl_easy *data) +{ + CURLMcode rc; + /* 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 + fine to start over and unmark the "deadness" of this handle */ + if(multi->num_alive) + return CURLM_ABORTED_BY_CALLBACK; + multi->dead = FALSE; + } + + /* Initialize timeout list for this handle */ + Curl_llist_init(&data->state.timeoutlist, NULL); + + /* + * No failure allowed in this function beyond this point. And no + * modification of easy nor multi handle allowed before this except for + * potential multi's connection cache growing which won't be undone in this + * function no matter what. + */ + if(data->set.errorbuffer) + data->set.errorbuffer[0] = 0; + + /* make the Curl_easy refer back to this multi handle - before Curl_expire() + is called. */ + data->multi = multi; + + /* Set the timeout for this handle to expire really soon so that it will + be taken care of even when this handle is added in the midst of operation + when only the curl_multi_socket() API is used. During that flow, only + sockets that time-out or have actions will be dealt with. Since this + handle has no action yet, we make sure it times out to get things to + happen. */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + + /* A somewhat crude work-around for a little glitch in Curl_update_timer() + that happens if the lastcall time is set to the same time when the handle + is removed as when the next handle is added, as then the check in + Curl_update_timer() that prevents calling the application multiple times + with the same timer info will not trigger and then the new handle's + timeout will not be notified to the app. + + The work-around is thus simply to clear the 'lastcall' variable to force + Curl_update_timer() to always trigger a callback to the app when a new + easy handle is added */ + memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); + + rc = Curl_update_timer(multi); + if(rc) + return rc; + + /* set the easy handle */ + multistate(data, MSTATE_INIT); + + /* for multi interface connections, we share DNS cache automatically if the + easy handle's one is currently not set. */ + if(!data->dns.hostcache || + (data->dns.hostcachetype == HCACHE_NONE)) { + data->dns.hostcache = &multi->hostcache; + data->dns.hostcachetype = HCACHE_MULTI; + } + + /* Point to the shared or multi handle connection cache */ + if(data->share && (data->share->specifier & (1<< CURL_LOCK_DATA_CONNECT))) + data->state.conn_cache = &data->share->conn_cache; + else + data->state.conn_cache = &multi->conn_cache; + data->state.lastconnect_id = -1; + +#ifdef USE_LIBPSL + /* Do the same for PSL. */ + if(data->share && (data->share->specifier & (1 << CURL_LOCK_DATA_PSL))) + data->psl = &data->share->psl; + else + data->psl = &multi->psl; +#endif + + link_easy(multi, data); + + /* increase the node-counter */ + multi->num_easy++; + + /* increase the alive-counter */ + multi->num_alive++; + + CONNCACHE_LOCK(data); + /* The closure handle only ever has default timeouts set. To improve the + state somewhat we clone the timeouts from each added handle so that the + closure handle always has the same timeouts as the most recently added + easy handle. */ + data->state.conn_cache->closure_handle->set.timeout = data->set.timeout; + data->state.conn_cache->closure_handle->set.server_response_timeout = + data->set.server_response_timeout; + data->state.conn_cache->closure_handle->set.no_signal = + data->set.no_signal; + data->id = data->state.conn_cache->next_easy_id++; + if(data->state.conn_cache->next_easy_id <= 0) + data->state.conn_cache->next_easy_id = 0; + CONNCACHE_UNLOCK(data); + + multi_warn_debug(multi, data); + + 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) +{ + struct Curl_sh_entry *sh = (struct Curl_sh_entry *)p; + + fprintf(stderr, " [readers %u][writers %u]", + sh->readers, sh->writers); +} +#endif + +static CURLcode multi_done(struct Curl_easy *data, + CURLcode status, /* an error if this is called + after an error was detected */ + bool premature) +{ + CURLcode result; + struct connectdata *conn = data->conn; + +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + DEBUGF(infof(data, "multi_done[%s]: status: %d prem: %d done: %d", + multi_statename[data->mstate], + (int)status, (int)premature, data->state.done)); +#else + DEBUGF(infof(data, "multi_done: status: %d prem: %d done: %d", + (int)status, (int)premature, data->state.done)); +#endif + + if(data->state.done) + /* Stop if multi_done() has already been called */ + return CURLE_OK; + + /* Stop the resolver and free its own resources (but not dns_entry yet). */ + Curl_resolver_kill(data); + + /* Cleanup possible redirect junk */ + Curl_safefree(data->req.newurl); + Curl_safefree(data->req.location); + + switch(status) { + case CURLE_ABORTED_BY_CALLBACK: + case CURLE_READ_ERROR: + case CURLE_WRITE_ERROR: + /* When we're aborted due to a callback return code it basically have to + be counted as premature as there is trouble ahead if we don't. We have + many callbacks and protocols work differently, we could potentially do + this more fine-grained in the future. */ + premature = TRUE; + default: + break; + } + + /* this calls the protocol-specific function pointer previously set */ + if(conn->handler->done) + result = conn->handler->done(data, status, premature); + else + result = status; + + if(CURLE_ABORTED_BY_CALLBACK != result) { + /* avoid this if we already aborted by callback to avoid this calling + another callback */ + int rc = Curl_pgrsDone(data); + if(!result && rc) + result = CURLE_ABORTED_BY_CALLBACK; + } + + /* Inform connection filters that this transfer is done */ + Curl_conn_ev_data_done(data, premature); + + process_pending_handles(data->multi); /* connection / multiplex */ + + Curl_safefree(data->state.ulbuf); + + Curl_client_cleanup(data); + + CONNCACHE_LOCK(data); + Curl_detach_connection(data); + if(CONN_INUSE(conn)) { + /* Stop if still used. */ + CONNCACHE_UNLOCK(data); + DEBUGF(infof(data, "Connection still in use %zu, " + "no more multi_done now!", + conn->easyq.size)); + return CURLE_OK; + } + + data->state.done = TRUE; /* called just now! */ + + if(conn->dns_entry) { + Curl_resolv_unlock(data, conn->dns_entry); /* done with this */ + conn->dns_entry = NULL; + } + Curl_hostcache_prune(data); + + /* if data->set.reuse_forbid is TRUE, it means the libcurl client has + forced us to close this connection. This is ignored for requests taking + place in a NTLM/NEGOTIATE authentication handshake + + if conn->bits.close is TRUE, it means that the connection should be + closed in spite of all our efforts to be nice, due to protocol + restrictions in our or the server's end + + if premature is TRUE, it means this connection was said to be DONE before + the entire request operation is complete and thus we can't know in what + state it is for reusing, so we're forced to close it. In a perfect world + we can add code that keep track of if we really must close it here or not, + but currently we have no such detail knowledge. + */ + + data->state.recent_conn_id = conn->connection_id; + if((data->set.reuse_forbid +#if defined(USE_NTLM) + && !(conn->http_ntlm_state == NTLMSTATE_TYPE2 || + conn->proxy_ntlm_state == NTLMSTATE_TYPE2) +#endif +#if defined(USE_SPNEGO) + && !(conn->http_negotiate_state == GSS_AUTHRECV || + conn->proxy_negotiate_state == GSS_AUTHRECV) +#endif + ) || conn->bits.close + || (premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) { + DEBUGF(infof(data, "multi_done, not reusing connection=%" + CURL_FORMAT_CURL_OFF_T ", forbid=%d" + ", close=%d, premature=%d, conn_multiplex=%d", + conn->connection_id, + data->set.reuse_forbid, conn->bits.close, premature, + Curl_conn_is_multiplex(conn, FIRSTSOCKET))); + connclose(conn, "disconnecting"); + Curl_conncache_remove_conn(data, conn, FALSE); + CONNCACHE_UNLOCK(data); + Curl_disconnect(data, conn, premature); + } + else { + char buffer[256]; + const char *host = +#ifndef CURL_DISABLE_PROXY + conn->bits.socksproxy ? + conn->socks_proxy.host.dispname : + conn->bits.httpproxy ? conn->http_proxy.host.dispname : +#endif + conn->bits.conn_to_host ? conn->conn_to_host.dispname : + conn->host.dispname; + /* create string before returning the connection */ + curl_off_t connection_id = conn->connection_id; + msnprintf(buffer, sizeof(buffer), + "Connection #%" CURL_FORMAT_CURL_OFF_T " to host %s left intact", + connection_id, host); + /* the connection is no longer in use by this transfer */ + CONNCACHE_UNLOCK(data); + if(Curl_conncache_return_conn(data, conn)) { + /* remember the most recently used connection */ + data->state.lastconnect_id = connection_id; + data->state.recent_conn_id = connection_id; + infof(data, "%s", buffer); + } + else + data->state.lastconnect_id = -1; + } + + Curl_safefree(data->state.buffer); + return result; +} + +static int close_connect_only(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + (void)param; + if(data->state.lastconnect_id != conn->connection_id) + return 0; + + if(!conn->connect_only) + return 1; + + connclose(conn, "Removing connect-only easy handle"); + + return 1; +} + +CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, + struct Curl_easy *data) +{ + struct Curl_easy *easy = data; + bool premature; + struct Curl_llist_element *e; + CURLMcode rc; + + /* 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! */ + + /* Prevent users from trying to remove an easy handle from the wrong multi */ + if(data->multi != multi) + return CURLM_BAD_EASY_HANDLE; + + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + premature = (data->mstate < MSTATE_COMPLETED) ? TRUE : FALSE; + + /* 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) { + /* this handle is "alive" so we need to count down the total number of + alive connections when this is removed */ + multi->num_alive--; + } + + 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) { + /* multi_done() clears the association between the easy handle and the + connection. + + Note that this ignores the return code simply because there's + 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 + called. Do it after multi_done() in case that sets another time! */ + Curl_expire_clear(data); + + if(data->connect_queue.ptr) { + /* the handle is in the pending or msgsent lists, so go ahead and remove + it */ + if(data->mstate == MSTATE_PENDING) + Curl_llist_remove(&multi->pending, &data->connect_queue, NULL); + else + Curl_llist_remove(&multi->msgsent, &data->connect_queue, NULL); + } + if(in_main_list(data)) + unlink_easy(multi, data); + + if(data->dns.hostcachetype == HCACHE_MULTI) { + /* stop using the multi handle's DNS cache, *after* the possible + multi_done() call above */ + data->dns.hostcache = NULL; + data->dns.hostcachetype = HCACHE_NONE; + } + + Curl_wildcard_dtor(&data->wildcard); + + /* change state without using multistate(), only to make singlesocket() do + what we want */ + data->mstate = MSTATE_COMPLETED; + + /* This ignores the return code even in case of problems because there's + nothing more to do about that, here */ + (void)singlesocket(multi, easy); /* to let the application know what sockets + that vanish with this handle */ + + /* Remove the association between the connection and the handle */ + Curl_detach_connection(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. And 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_conncache_remove_conn(data, c, TRUE); + Curl_disconnect(data, c, TRUE); + } + } + + if(data->state.lastconnect_id != -1) { + /* Mark any connect-only connection for closure */ + Curl_conncache_foreach(data, data->state.conn_cache, + NULL, close_connect_only); + } + +#ifdef USE_LIBPSL + /* Remove the PSL association. */ + if(data->psl == &multi->psl) + data->psl = NULL; +#endif + + /* as this was using a shared connection cache we clear the pointer to that + since we're not part of that multi handle anymore */ + data->state.conn_cache = NULL; + + data->multi = NULL; /* clear the association to this multi handle */ + + /* make sure there's no pending message in the queue sent from this easy + handle */ + for(e = multi->msglist.head; e; e = e->next) { + struct Curl_message *msg = e->ptr; + + if(msg->extmsg.easy_handle == easy) { + Curl_llist_remove(&multi->msglist, e, NULL); + /* there can only be one from this specific handle */ + break; + } + } + + /* NOTE NOTE NOTE + We do not touch the easy handle here! */ + multi->num_easy--; /* one less to care about now */ + + process_pending_handles(multi); + + rc = Curl_update_timer(multi); + if(rc) + return rc; + return CURLM_OK; +} + +/* Return TRUE if the application asked for multiplexing */ +bool Curl_multiplex_wanted(const struct Curl_multi *multi) +{ + return (multi && (multi->multiplexing)); +} + +/* + * Curl_detach_connection() removes the given transfer from the connection. + * + * This is the only function that should clear data->conn. This will + * occasionally be called with the data->conn pointer already cleared. + */ +void Curl_detach_connection(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + if(conn) { + Curl_conn_ev_data_detach(conn, data); + Curl_llist_remove(&conn->easyq, &data->conn_queue, NULL); + } + data->conn = NULL; +} + +/* + * Curl_attach_connection() attaches this transfer to this connection. + * + * This is the only function that should assign data->conn + */ +void Curl_attach_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + DEBUGASSERT(!data->conn); + DEBUGASSERT(conn); + data->conn = conn; + Curl_llist_insert_next(&conn->easyq, conn->easyq.tail, data, + &data->conn_queue); + if(conn->handler && conn->handler->attach) + conn->handler->attach(data, conn); + Curl_conn_ev_data_attach(conn, data); +} + +static int domore_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) +{ + if(conn && conn->handler->domore_getsock) + return conn->handler->domore_getsock(data, conn, socks); + return GETSOCK_BLANK; +} + +static int doing_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) +{ + if(conn && conn->handler->doing_getsock) + return conn->handler->doing_getsock(data, conn, socks); + return GETSOCK_BLANK; +} + +static int protocol_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) +{ + if(conn->handler->proto_getsock) + return conn->handler->proto_getsock(data, conn, socks); + return GETSOCK_BLANK; +} + +/* Initializes `poll_set` with the current socket poll actions needed + * for transfer `data`. */ +static void multi_getsock(struct Curl_easy *data, + struct easy_pollset *ps) +{ + /* The no connection case can happen when this is called from + curl_multi_remove_handle() => singlesocket() => multi_getsock(). + */ + Curl_pollset_reset(data, ps); + if(!data->conn) + return; + + switch(data->mstate) { + default: + break; + + case MSTATE_RESOLVING: + Curl_pollset_add_socks2(data, ps, Curl_resolv_getsock); + /* connection filters are not involved in this phase */ + return; + + case MSTATE_PROTOCONNECTING: + case MSTATE_PROTOCONNECT: + Curl_pollset_add_socks(data, ps, protocol_getsock); + break; + + case MSTATE_DO: + case MSTATE_DOING: + Curl_pollset_add_socks(data, ps, doing_getsock); + break; + + case MSTATE_TUNNELING: + case MSTATE_CONNECTING: + break; + + case MSTATE_DOING_MORE: + Curl_pollset_add_socks(data, ps, domore_getsock); + break; + + case MSTATE_DID: /* since is set after DO is completed, we switch to + waiting for the same as the PERFORMING state */ + case MSTATE_PERFORMING: + Curl_pollset_add_socks(data, ps, Curl_single_getsock); + break; + + case MSTATE_RATELIMITING: + /* nothing to wait for */ + return; + } + + /* Let connection filters add/remove as needed */ + Curl_conn_adjust_pollset(data, ps); +} + +CURLMcode curl_multi_fdset(struct Curl_multi *multi, + 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. */ + struct Curl_easy *data; + int this_max_fd = -1; + struct easy_pollset ps; + unsigned int i; + (void)exc_fd_set; /* not used */ + + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + memset(&ps, 0, sizeof(ps)); + for(data = multi->easyp; data; data = data->next) { + multi_getsock(data, &ps); + + for(i = 0; i < ps.num; i++) { + if(!FDSET_SOCK(ps.sockets[i])) + /* pretend it doesn't 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]; + } + } + + *max_fd = this_max_fd; + + return CURLM_OK; +} + +#ifdef USE_WINSOCK +/* Reset FD_WRITE for TCP sockets. Nothing is actually sent. UDP sockets can't + * be reset this way because an empty datagram would be sent. #9203 + * + * "On Windows the internal state of FD_WRITE as returned from + * WSAEnumNetworkEvents is only reset after successful send()." + */ +static void reset_socket_fdwrite(curl_socket_t s) +{ + int t; + int l = (int)sizeof(t); + if(!getsockopt(s, SOL_SOCKET, SO_TYPE, (char *)&t, &l) && t == SOCK_STREAM) + send(s, NULL, 0, 0); +} +#endif + +#define NUM_POLLS_ON_STACK 10 + +static CURLMcode multi_wait(struct Curl_multi *multi, + struct curl_waitfd extra_fds[], + unsigned int extra_nfds, + int timeout_ms, + int *ret, + bool extrawait, /* when no socket, wait */ + bool use_wakeup) +{ + struct Curl_easy *data; + struct easy_pollset ps; + size_t i; + unsigned int nfds = 0; + unsigned int curlfds; + long timeout_internal; + int retcode = 0; + struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; + struct pollfd *ufds = &a_few_on_stack[0]; + bool ufds_malloc = FALSE; +#ifdef USE_WINSOCK + WSANETWORKEVENTS wsa_events; + DEBUGASSERT(multi->wsa_event != WSA_INVALID_EVENT); +#endif +#ifndef ENABLE_WAKEUP + (void)use_wakeup; +#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; + + /* Count up how many fds we have from the multi handle */ + memset(&ps, 0, sizeof(ps)); + for(data = multi->easyp; data; data = data->next) { + multi_getsock(data, &ps); + nfds += ps.num; + } + + /* If the internally desired timeout is actually shorter than requested from + the outside, then use the shorter time! But only if the internal timer + is actually larger than -1! */ + (void)multi_timeout(multi, &timeout_internal); + if((timeout_internal >= 0) && (timeout_internal < (long)timeout_ms)) + timeout_ms = (int)timeout_internal; + + curlfds = nfds; /* number of internal file descriptors */ + nfds += extra_nfds; /* add the externally provided ones */ + +#ifdef ENABLE_WAKEUP +#ifdef USE_WINSOCK + if(use_wakeup) { +#else + if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { +#endif + ++nfds; + } +#endif + + if(nfds > NUM_POLLS_ON_STACK) { + /* 'nfds' is a 32 bit value and 'struct pollfd' is typically 8 bytes + big, so at 2^29 sockets this value might wrap. When a process gets + the capability to actually handle over 500 million sockets this + calculation needs a integer overflow check. */ + ufds = malloc(nfds * sizeof(struct pollfd)); + if(!ufds) + return CURLM_OUT_OF_MEMORY; + ufds_malloc = TRUE; + } + nfds = 0; + + /* only do the second loop if we found descriptors in the first stage run + above */ + + if(curlfds) { + /* Add the curl handles to our pollfds first */ + for(data = multi->easyp; data; data = data->next) { + multi_getsock(data, &ps); + + for(i = 0; i < ps.num; i++) { + struct pollfd *ufd = &ufds[nfds++]; +#ifdef USE_WINSOCK + long mask = 0; +#endif + ufd->fd = ps.sockets[i]; + ufd->events = 0; + if(ps.actions[i] & CURL_POLL_IN) { +#ifdef USE_WINSOCK + mask |= FD_READ|FD_ACCEPT|FD_CLOSE; +#endif + ufd->events |= POLLIN; + } + if(ps.actions[i] & CURL_POLL_OUT) { +#ifdef USE_WINSOCK + mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; + reset_socket_fdwrite(ps.sockets[i]); +#endif + ufd->events |= POLLOUT; + } +#ifdef USE_WINSOCK + if(WSAEventSelect(ps.sockets[i], multi->wsa_event, mask) != 0) { + if(ufds_malloc) + free(ufds); + return CURLM_INTERNAL_ERROR; + } +#endif + } + } + } + + /* Add external file descriptions from poll-like struct curl_waitfd */ + for(i = 0; i < extra_nfds; i++) { +#ifdef USE_WINSOCK + long mask = 0; + if(extra_fds[i].events & CURL_WAIT_POLLIN) + mask |= FD_READ|FD_ACCEPT|FD_CLOSE; + if(extra_fds[i].events & CURL_WAIT_POLLPRI) + mask |= FD_OOB; + if(extra_fds[i].events & CURL_WAIT_POLLOUT) { + mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; + reset_socket_fdwrite(extra_fds[i].fd); + } + if(WSAEventSelect(extra_fds[i].fd, multi->wsa_event, mask) != 0) { + if(ufds_malloc) + free(ufds); + return CURLM_INTERNAL_ERROR; + } +#endif + ufds[nfds].fd = extra_fds[i].fd; + ufds[nfds].events = 0; + if(extra_fds[i].events & CURL_WAIT_POLLIN) + ufds[nfds].events |= POLLIN; + if(extra_fds[i].events & CURL_WAIT_POLLPRI) + ufds[nfds].events |= POLLPRI; + if(extra_fds[i].events & CURL_WAIT_POLLOUT) + ufds[nfds].events |= POLLOUT; + ++nfds; + } + +#ifdef ENABLE_WAKEUP +#ifndef USE_WINSOCK + if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { + ufds[nfds].fd = multi->wakeup_pair[0]; + ufds[nfds].events = POLLIN; + ++nfds; + } +#endif +#endif + +#if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK) + if(nfds || use_wakeup) { +#else + if(nfds) { +#endif + int pollrc; +#ifdef USE_WINSOCK + if(nfds) + pollrc = Curl_poll(ufds, nfds, 0); /* just pre-check with WinSock */ + else + pollrc = 0; +#else + pollrc = Curl_poll(ufds, nfds, timeout_ms); /* wait... */ +#endif + if(pollrc < 0) + return CURLM_UNRECOVERABLE_POLL; + + if(pollrc > 0) { + retcode = pollrc; +#ifdef USE_WINSOCK + } + else { /* now wait... if not ready during the pre-check (pollrc == 0) */ + WSAWaitForMultipleEvents(1, &multi->wsa_event, FALSE, timeout_ms, FALSE); + } + /* With WinSock, we have to run the following section unconditionally + to call WSAEventSelect(fd, event, 0) on all the sockets */ + { +#endif + /* copy revents results from the poll to the curl_multi_wait poll + struct, the bit values of the actual underlying poll() implementation + may not be the same as the ones in the public libcurl API! */ + for(i = 0; i < extra_nfds; i++) { + unsigned r = ufds[curlfds + i].revents; + unsigned short mask = 0; +#ifdef USE_WINSOCK + curl_socket_t s = extra_fds[i].fd; + wsa_events.lNetworkEvents = 0; + if(WSAEnumNetworkEvents(s, NULL, &wsa_events) == 0) { + if(wsa_events.lNetworkEvents & (FD_READ|FD_ACCEPT|FD_CLOSE)) + mask |= CURL_WAIT_POLLIN; + if(wsa_events.lNetworkEvents & (FD_WRITE|FD_CONNECT|FD_CLOSE)) + mask |= CURL_WAIT_POLLOUT; + if(wsa_events.lNetworkEvents & FD_OOB) + mask |= CURL_WAIT_POLLPRI; + if(ret && !pollrc && wsa_events.lNetworkEvents) + retcode++; + } + WSAEventSelect(s, multi->wsa_event, 0); + if(!pollrc) { + extra_fds[i].revents = mask; + continue; + } +#endif + if(r & POLLIN) + mask |= CURL_WAIT_POLLIN; + if(r & POLLOUT) + mask |= CURL_WAIT_POLLOUT; + if(r & POLLPRI) + mask |= CURL_WAIT_POLLPRI; + extra_fds[i].revents = mask; + } + +#ifdef USE_WINSOCK + /* Count up all our own sockets that had activity, + and remove them from the event. */ + if(curlfds) { + + for(data = multi->easyp; data; data = data->next) { + multi_getsock(data, &ps); + + for(i = 0; i < ps.num; i++) { + wsa_events.lNetworkEvents = 0; + if(WSAEnumNetworkEvents(ps.sockets[i], NULL, + &wsa_events) == 0) { + if(ret && !pollrc && wsa_events.lNetworkEvents) + retcode++; + } + WSAEventSelect(ps.sockets[i], multi->wsa_event, 0); + } + } + } + + WSAResetEvent(multi->wsa_event); +#else +#ifdef ENABLE_WAKEUP + if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { + if(ufds[curlfds + extra_nfds].revents & POLLIN) { + char buf[64]; + ssize_t nread; + while(1) { + /* the reading socket is non-blocking, try to read + data from it until it receives an error (except EINTR). + In normal cases it will get EAGAIN or EWOULDBLOCK + when there is no more data, breaking the loop. */ + nread = wakeup_read(multi->wakeup_pair[0], buf, sizeof(buf)); + if(nread <= 0) { + if(nread < 0 && EINTR == SOCKERRNO) + continue; + break; + } + } + /* do not count the wakeup socket into the returned value */ + retcode--; + } + } +#endif +#endif + } + } + + if(ufds_malloc) + free(ufds); + if(ret) + *ret = retcode; +#if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK) + if(extrawait && !nfds && !use_wakeup) { +#else + if(extrawait && !nfds) { +#endif + long sleep_ms = 0; + + /* Avoid busy-looping when there's nothing particular to wait for */ + if(!curl_multi_timeout(multi, &sleep_ms) && sleep_ms) { + if(sleep_ms > timeout_ms) + sleep_ms = timeout_ms; + /* when there are no easy handles in the multi, this holds a -1 + timeout */ + else if(sleep_ms < 0) + sleep_ms = timeout_ms; + Curl_wait_ms(sleep_ms); + } + } + + return CURLM_OK; +} + +CURLMcode curl_multi_wait(struct Curl_multi *multi, + struct curl_waitfd extra_fds[], + unsigned int extra_nfds, + int timeout_ms, + int *ret) +{ + return multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, FALSE, + FALSE); +} + +CURLMcode curl_multi_poll(struct Curl_multi *multi, + struct curl_waitfd extra_fds[], + unsigned int extra_nfds, + int timeout_ms, + int *ret) +{ + return multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, TRUE, + TRUE); +} + +CURLMcode curl_multi_wakeup(struct Curl_multi *multi) +{ + /* this function is usually called from another thread, + it has to be careful only to access parts of the + Curl_multi struct that are constant */ + + /* GOOD_MULTI_HANDLE can be safely called */ + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + +#ifdef ENABLE_WAKEUP +#ifdef USE_WINSOCK + if(WSASetEvent(multi->wsa_event)) + return CURLM_OK; +#else + /* the wakeup_pair variable is only written during init and cleanup, + making it safe to access from another thread after the init part + and before cleanup */ + if(multi->wakeup_pair[1] != CURL_SOCKET_BAD) { + char buf[1]; + buf[0] = 1; + while(1) { + /* swrite() is not thread-safe in general, because concurrent calls + can have their messages interleaved, but in this case the content + of the messages does not matter, which makes it ok to call. + + The write socket is set to non-blocking, this way this function + cannot block, making it safe to call even from the same thread + that will call curl_multi_wait(). If swrite() returns that it + would block, it's considered successful because it means that + previous calls to this function will wake up the poll(). */ + if(wakeup_write(multi->wakeup_pair[1], buf, sizeof(buf)) < 0) { + int err = SOCKERRNO; + int return_success; +#ifdef USE_WINSOCK + return_success = WSAEWOULDBLOCK == err; +#else + if(EINTR == err) + continue; + return_success = EWOULDBLOCK == err || EAGAIN == err; +#endif + if(!return_success) + return CURLM_WAKEUP_FAILURE; + } + return CURLM_OK; + } + } +#endif +#endif + return CURLM_WAKEUP_FAILURE; +} + +/* + * multi_ischanged() is called + * + * Returns TRUE/FALSE whether the state is changed to trigger a CONNECT_PEND + * => CONNECT action. + * + * Set 'clear' to TRUE to have it also clear the state variable. + */ +static bool multi_ischanged(struct Curl_multi *multi, bool clear) +{ + bool retval = multi->recheckstate; + if(clear) + multi->recheckstate = FALSE; + return retval; +} + +/* + * Curl_multi_connchanged() is called to tell that there is a connection in + * this multi handle that has changed state (multiplexing become possible, the + * number of allowed streams changed or similar), and a subsequent use of this + * multi handle should move CONNECT_PEND handles back to CONNECT to have them + * retry. + */ +void Curl_multi_connchanged(struct Curl_multi *multi) +{ + multi->recheckstate = TRUE; +} + +CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn) +{ + CURLMcode rc; + + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + rc = curl_multi_add_handle(multi, data); + if(!rc) { + struct SingleRequest *k = &data->req; + + /* pass in NULL for 'conn' here since we don't want to init the + connection, only this transfer */ + Curl_init_do(data, NULL); + + /* take this handle to the perform state right away */ + multistate(data, MSTATE_PERFORMING); + Curl_attach_connection(data, conn); + k->keepon |= KEEP_RECV; /* setup to receive! */ + } + return rc; +} + +static CURLcode multi_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + DEBUGASSERT(conn); + DEBUGASSERT(conn->handler); + + if(conn->handler->do_it) + result = conn->handler->do_it(data, done); + + return result; +} + +/* + * multi_do_more() is called during the DO_MORE multi state. It is basically a + * second stage DO state which (wrongly) was introduced to support FTP's + * second connection. + * + * 'complete' can return 0 for incomplete, 1 for done and -1 for go back to + * DOING state there's more work to do! + */ + +static CURLcode multi_do_more(struct Curl_easy *data, int *complete) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + *complete = 0; + + if(conn->handler->do_more) + result = conn->handler->do_more(data, complete); + + return result; +} + +/* + * Check whether a timeout occurred, and handle it if it did + */ +static bool multi_handle_timeout(struct Curl_easy *data, + struct curltime *now, + bool *stream_error, + CURLcode *result, + bool connect_timeout) +{ + timediff_t timeout_ms; + timeout_ms = Curl_timeleft(data, now, connect_timeout); + + if(timeout_ms < 0) { + /* Handle timed out */ + if(data->mstate == MSTATE_RESOLVING) + failf(data, "Resolving timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds", + Curl_timediff(*now, data->progress.t_startsingle)); + else if(data->mstate == MSTATE_CONNECTING) + failf(data, "Connection timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds", + Curl_timediff(*now, data->progress.t_startsingle)); + else { + struct SingleRequest *k = &data->req; + if(k->size != -1) { + failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %" + CURL_FORMAT_CURL_OFF_T " bytes received", + Curl_timediff(*now, data->progress.t_startsingle), + k->bytecount, k->size); + } + else { + failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds with %" CURL_FORMAT_CURL_OFF_T + " bytes received", + Curl_timediff(*now, data->progress.t_startsingle), + k->bytecount); + } + } + + /* Force connection closed if the connection has indeed been used */ + if(data->mstate > MSTATE_DO) { + streamclose(data->conn, "Disconnected with pending data"); + *stream_error = TRUE; + } + *result = CURLE_OPERATION_TIMEDOUT; + (void)multi_done(data, *result, TRUE); + } + + return (timeout_ms < 0); +} + +/* + * We are doing protocol-specific connecting and this is being called over and + * over from the multi interface until the connection phase is done on + * protocol layer. + */ + +static CURLcode protocol_connecting(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + if(conn && conn->handler->connecting) { + *done = FALSE; + result = conn->handler->connecting(data, done); + } + else + *done = TRUE; + + return result; +} + +/* + * We are DOING this is being called over and over from the multi interface + * until the DOING phase is done on protocol layer. + */ + +static CURLcode protocol_doing(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + if(conn && conn->handler->doing) { + *done = FALSE; + result = conn->handler->doing(data, done); + } + else + *done = TRUE; + + return result; +} + +/* + * We have discovered that the TCP connection has been successful, we can now + * proceed with some action. + * + */ +static CURLcode protocol_connect(struct Curl_easy *data, + bool *protocol_done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + DEBUGASSERT(conn); + DEBUGASSERT(protocol_done); + + *protocol_done = FALSE; + + if(Curl_conn_is_connected(conn, FIRSTSOCKET) + && conn->bits.protoconnstart) { + /* We already are connected, get back. This may happen when the connect + worked fine in the first call, like when we connect to a local server + or proxy. Note that we don't know if the protocol is actually done. + + Unless this protocol doesn't have any protocol-connect callback, as + then we know we're done. */ + if(!conn->handler->connecting) + *protocol_done = TRUE; + + return CURLE_OK; + } + + if(!conn->bits.protoconnstart) { + if(conn->handler->connect_it) { + /* is there a protocol-specific connect() procedure? */ + + /* Call the protocol-specific connect function */ + result = conn->handler->connect_it(data, protocol_done); + } + else + *protocol_done = TRUE; + + /* it has started, possibly even completed but that knowledge isn't stored + in this bit! */ + if(!result) + conn->bits.protoconnstart = TRUE; + } + + return result; /* pass back status */ +} + +/* + * readrewind() rewinds the read stream. This is typically used for HTTP + * POST/PUT with multi-pass authentication when a sending was denied and a + * resend is necessary. + */ +static CURLcode readrewind(struct Curl_easy *data) +{ + curl_mimepart *mimepart = &data->set.mimepost; + DEBUGASSERT(data->conn); + + data->state.rewindbeforesend = FALSE; /* we rewind now */ + + /* explicitly switch off sending data on this connection now since we are + about to restart a new transfer and thus we want to avoid inadvertently + sending more data on the existing connection until the next transfer + starts */ + data->req.keepon &= ~KEEP_SEND; + + /* We have sent away data. If not using CURLOPT_POSTFIELDS or + CURLOPT_HTTPPOST, call app to rewind + */ +#ifndef CURL_DISABLE_HTTP + if(data->conn->handler->protocol & PROTO_FAMILY_HTTP) { + if(data->state.mimepost) + mimepart = data->state.mimepost; + } +#endif + if(data->set.postfields || + (data->state.httpreq == HTTPREQ_GET) || + (data->state.httpreq == HTTPREQ_HEAD)) + ; /* no need to rewind */ + else if(data->state.httpreq == HTTPREQ_POST_MIME || + data->state.httpreq == HTTPREQ_POST_FORM) { + CURLcode result = Curl_mime_rewind(mimepart); + if(result) { + failf(data, "Cannot rewind mime/post data"); + return result; + } + } + else { + if(data->set.seek_func) { + 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); + if(err) { + failf(data, "seek callback returned error %d", (int)err); + return CURLE_SEND_FAIL_REWIND; + } + } + else if(data->set.ioctl_func) { + 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); + infof(data, "the ioctl callback returned %d", (int)err); + + if(err) { + failf(data, "ioctl callback returned error %d", (int)err); + return CURLE_SEND_FAIL_REWIND; + } + } + else { + /* If no CURLOPT_READFUNCTION is used, we know that we operate on a + given FILE * stream and we can actually attempt to rewind that + ourselves with fseek() */ + if(data->state.fread_func == (curl_read_callback)fread) { + if(-1 != fseek(data->state.in, 0, SEEK_SET)) + /* successful rewind */ + return CURLE_OK; + } + + /* no callback set or failure above, makes us fail at once */ + failf(data, "necessary data rewind wasn't possible"); + return CURLE_SEND_FAIL_REWIND; + } + } + return CURLE_OK; +} + +/* + * Curl_preconnect() is called immediately before a connect starts. When a + * redirect is followed, this is then called multiple times during a single + * transfer. + */ +CURLcode Curl_preconnect(struct Curl_easy *data) +{ + if(!data->state.buffer) { + data->state.buffer = malloc(data->set.buffer_size + 1); + if(!data->state.buffer) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +static void set_in_callback(struct Curl_multi *multi, bool value) +{ + multi->in_callback = value; +} + +static CURLMcode multi_runsingle(struct Curl_multi *multi, + struct curltime *nowp, + struct Curl_easy *data) +{ + struct Curl_message *msg = NULL; + bool connected; + bool async; + bool protocol_connected = FALSE; + bool dophase_done = FALSE; + bool done = FALSE; + CURLMcode rc; + CURLcode result = CURLE_OK; + timediff_t recv_timeout_ms; + timediff_t send_timeout_ms; + int control; + + if(!GOOD_EASY_HANDLE(data)) + return CURLM_BAD_EASY_HANDLE; + + if(multi->dead) { + /* a multi-level callback returned error before, meaning every individual + transfer now has failed */ + result = CURLE_ABORTED_BY_CALLBACK; + Curl_posttransfer(data); + multi_done(data, result, FALSE); + multistate(data, MSTATE_COMPLETED); + } + + multi_warn_debug(multi, data); + + do { + /* A "stream" here is a logical stream if the protocol can handle that + (HTTP/2), or the full connection for older protocols */ + bool stream_error = FALSE; + rc = CURLM_OK; + + if(multi_ischanged(multi, TRUE)) { + DEBUGF(infof(data, "multi changed, check CONNECT_PEND queue")); + process_pending_handles(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 && + (data->mstate >= MSTATE_CONNECT) && + (data->mstate < MSTATE_COMPLETED)) { + /* Check for overall operation timeout here but defer handling the + * connection timeout to later, to allow for a connection to be set up + * in the window since we last checked timeout. This prevents us + * tearing down a completed connection in the case where we were slow + * to check the timeout (e.g. process descheduled during this loop). + * We set connect_timeout=FALSE to do this. */ + + /* we need to wait for the connect state as only then is the start time + stored, but we must not check already completed handles */ + if(multi_handle_timeout(data, nowp, &stream_error, &result, FALSE)) { + /* Skip the statemachine and go directly to error handling section. */ + goto statemachine_end; + } + } + + switch(data->mstate) { + case MSTATE_INIT: + /* init this transfer. */ + result = Curl_pretransfer(data); + + if(!result) { + /* after init, go CONNECT */ + multistate(data, MSTATE_CONNECT); + *nowp = Curl_pgrsTime(data, TIMER_STARTOP); + rc = CURLM_CALL_MULTI_PERFORM; + } + break; + + case MSTATE_CONNECT: + /* Connect. We want to get a connection identifier filled in. */ + /* init this transfer. */ + result = Curl_preconnect(data); + if(result) + break; + + *nowp = Curl_pgrsTime(data, TIMER_STARTSINGLE); + if(data->set.timeout) + Curl_expire(data, data->set.timeout, EXPIRE_TIMEOUT); + + if(data->set.connecttimeout) + Curl_expire(data, data->set.connecttimeout, EXPIRE_CONNECTTIMEOUT); + + result = Curl_connect(data, &async, &connected); + if(CURLE_NO_CONNECTION_AVAILABLE == result) { + /* There was no connection available. We will go to the pending + state and wait for an available connection. */ + multistate(data, MSTATE_PENDING); + + /* add this handle to the list of connect-pending handles */ + Curl_llist_insert_next(&multi->pending, multi->pending.tail, data, + &data->connect_queue); + /* unlink from the main list */ + unlink_easy(multi, data); + result = CURLE_OK; + break; + } + else if(data->state.previouslypending) { + /* this transfer comes from the pending queue so try move another */ + infof(data, "Transfer was pending, now try another"); + process_pending_handles(data->multi); + } + + if(!result) { + if(async) + /* We're now waiting for an asynchronous name lookup */ + multistate(data, MSTATE_RESOLVING); + else { + /* after the connect has been sent off, go WAITCONNECT unless the + protocol connect is already done and we can go directly to + WAITDO or DO! */ + rc = CURLM_CALL_MULTI_PERFORM; + + if(connected) + multistate(data, MSTATE_PROTOCONNECT); + else { + multistate(data, MSTATE_CONNECTING); + } + } + } + break; + + case MSTATE_RESOLVING: + /* awaiting an asynch name resolve to complete */ + { + struct Curl_dns_entry *dns = NULL; + struct connectdata *conn = data->conn; + const char *hostname; + + DEBUGASSERT(conn); +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy) + hostname = conn->http_proxy.host.name; + else +#endif + if(conn->bits.conn_to_host) + hostname = conn->conn_to_host.name; + else + hostname = conn->host.name; + + /* check if we have the name resolved by now */ + dns = Curl_fetch_addr(data, hostname, (int)conn->port); + + if(dns) { +#ifdef CURLRES_ASYNCH + conn->resolve_async.dns = dns; + conn->resolve_async.done = TRUE; +#endif + result = CURLE_OK; + infof(data, "Hostname '%s' was found in DNS cache", hostname); + } + + if(!dns) + result = Curl_resolv_check(data, &dns); + + /* Update sockets here, because the socket(s) may have been + closed and the application thus needs to be told, even if it + is likely that the same socket(s) will again be used further + down. If the name has not yet been resolved, it is likely + that new sockets have been opened in an attempt to contact + another resolver. */ + rc = singlesocket(multi, data); + if(rc) + return rc; + + if(dns) { + /* Perform the next step in the connection phase, and then move on + to the WAITCONNECT state */ + result = Curl_once_resolved(data, &connected); + + if(result) + /* if Curl_once_resolved() returns failure, the connection struct + is already freed and gone */ + data->conn = NULL; /* no more connection */ + else { + /* call again please so that we get the next socket setup */ + rc = CURLM_CALL_MULTI_PERFORM; + if(connected) + multistate(data, MSTATE_PROTOCONNECT); + else { + multistate(data, MSTATE_CONNECTING); + } + } + } + + if(result) { + /* failure detected */ + stream_error = TRUE; + break; + } + } + break; + +#ifndef CURL_DISABLE_HTTP + case MSTATE_TUNNELING: + /* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */ + DEBUGASSERT(data->conn); + result = Curl_http_connect(data, &protocol_connected); +#ifndef CURL_DISABLE_PROXY + if(data->conn->bits.proxy_connect_closed) { + rc = CURLM_CALL_MULTI_PERFORM; + /* connect back to proxy again */ + result = CURLE_OK; + multi_done(data, CURLE_OK, FALSE); + multistate(data, MSTATE_CONNECT); + } + else +#endif + if(!result) { + rc = CURLM_CALL_MULTI_PERFORM; + /* initiate protocol connect phase */ + multistate(data, MSTATE_PROTOCONNECT); + } + else + stream_error = TRUE; + break; +#endif + + case MSTATE_CONNECTING: + /* awaiting a completion of an asynch TCP connect */ + DEBUGASSERT(data->conn); + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &connected); + if(connected && !result) { + rc = CURLM_CALL_MULTI_PERFORM; + multistate(data, MSTATE_PROTOCONNECT); + } + else if(result) { + /* failure detected */ + Curl_posttransfer(data); + multi_done(data, result, TRUE); + stream_error = TRUE; + break; + } + break; + + case MSTATE_PROTOCONNECT: + if(data->state.rewindbeforesend) + result = readrewind(data); + + if(!result && data->conn->bits.reuse) { + /* ftp seems to hang when protoconnect on reused connection + * since we handle PROTOCONNECT in general inside the filers, it + * seems wrong to restart this on a reused connection. */ + multistate(data, MSTATE_DO); + rc = CURLM_CALL_MULTI_PERFORM; + break; + } + if(!result) + result = protocol_connect(data, &protocol_connected); + if(!result && !protocol_connected) { + /* switch to waiting state */ + multistate(data, MSTATE_PROTOCONNECTING); + rc = CURLM_CALL_MULTI_PERFORM; + } + else if(!result) { + /* protocol connect has completed, go WAITDO or DO */ + multistate(data, MSTATE_DO); + rc = CURLM_CALL_MULTI_PERFORM; + } + else { + /* failure detected */ + Curl_posttransfer(data); + multi_done(data, result, TRUE); + stream_error = TRUE; + } + break; + + case MSTATE_PROTOCONNECTING: + /* protocol-specific connect phase */ + result = protocol_connecting(data, &protocol_connected); + if(!result && protocol_connected) { + /* after the connect has completed, go WAITDO or DO */ + multistate(data, MSTATE_DO); + rc = CURLM_CALL_MULTI_PERFORM; + } + else if(result) { + /* failure detected */ + Curl_posttransfer(data); + multi_done(data, result, TRUE); + stream_error = TRUE; + } + break; + + case MSTATE_DO: + if(data->set.fprereq) { + int prereq_rc; + + /* call the prerequest callback function */ + Curl_set_in_callback(data, true); + prereq_rc = data->set.fprereq(data->set.prereq_userp, + data->info.conn_primary_ip, + data->info.conn_local_ip, + data->info.conn_primary_port, + data->info.conn_local_port); + Curl_set_in_callback(data, false); + if(prereq_rc != CURL_PREREQFUNC_OK) { + failf(data, "operation aborted by pre-request callback"); + /* failure in pre-request callback - don't do any other processing */ + result = CURLE_ABORTED_BY_CALLBACK; + Curl_posttransfer(data); + multi_done(data, result, FALSE); + stream_error = TRUE; + break; + } + } + + if(data->set.connect_only == 1) { + /* keep connection open for application to use the socket */ + connkeep(data->conn, "CONNECT_ONLY"); + multistate(data, MSTATE_DONE); + result = CURLE_OK; + rc = CURLM_CALL_MULTI_PERFORM; + } + else { + /* Perform the protocol's DO action */ + result = multi_do(data, &dophase_done); + + /* When multi_do() returns failure, data->conn might be NULL! */ + + if(!result) { + if(!dophase_done) { +#ifndef CURL_DISABLE_FTP + /* some steps needed for wildcard matching */ + if(data->state.wildcardmatch) { + struct WildcardData *wc = data->wildcard; + if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) { + /* skip some states if it is important */ + multi_done(data, CURLE_OK, FALSE); + + /* if there's no connection left, skip the DONE state */ + multistate(data, data->conn ? + MSTATE_DONE : MSTATE_COMPLETED); + rc = CURLM_CALL_MULTI_PERFORM; + break; + } + } +#endif + /* DO was not completed in one function call, we must continue + DOING... */ + multistate(data, MSTATE_DOING); + rc = CURLM_CALL_MULTI_PERFORM; + } + + /* after DO, go DO_DONE... or DO_MORE */ + else if(data->conn->bits.do_more) { + /* we're supposed to do more, but we need to sit down, relax + and wait a little while first */ + multistate(data, MSTATE_DOING_MORE); + rc = CURLM_CALL_MULTI_PERFORM; + } + else { + /* we're done with the DO, now DID */ + multistate(data, MSTATE_DID); + rc = CURLM_CALL_MULTI_PERFORM; + } + } + else if((CURLE_SEND_ERROR == result) && + data->conn->bits.reuse) { + /* + * In this situation, a connection that we were trying to use + * may have unexpectedly died. If possible, send the connection + * back to the CONNECT phase so we can try again. + */ + char *newurl = NULL; + followtype follow = FOLLOW_NONE; + CURLcode drc; + + drc = Curl_retry_request(data, &newurl); + if(drc) { + /* a failure here pretty much implies an out of memory */ + result = drc; + stream_error = TRUE; + } + + Curl_posttransfer(data); + drc = multi_done(data, result, FALSE); + + /* When set to retry the connection, we must go back to the CONNECT + * state */ + if(newurl) { + if(!drc || (drc == CURLE_SEND_ERROR)) { + follow = FOLLOW_RETRY; + drc = Curl_follow(data, newurl, follow); + if(!drc) { + multistate(data, MSTATE_CONNECT); + rc = CURLM_CALL_MULTI_PERFORM; + result = CURLE_OK; + } + else { + /* Follow failed */ + result = drc; + } + } + else { + /* done didn't return OK or SEND_ERROR */ + result = drc; + } + } + else { + /* Have error handler disconnect conn if we can't retry */ + stream_error = TRUE; + } + free(newurl); + } + else { + /* failure detected */ + Curl_posttransfer(data); + if(data->conn) + multi_done(data, result, FALSE); + stream_error = TRUE; + } + } + break; + + case MSTATE_DOING: + /* we continue DOING until the DO phase is complete */ + DEBUGASSERT(data->conn); + result = protocol_doing(data, &dophase_done); + if(!result) { + if(dophase_done) { + /* after DO, go DO_DONE or DO_MORE */ + multistate(data, data->conn->bits.do_more? + MSTATE_DOING_MORE : MSTATE_DID); + rc = CURLM_CALL_MULTI_PERFORM; + } /* dophase_done */ + } + else { + /* failure detected */ + Curl_posttransfer(data); + multi_done(data, result, FALSE); + stream_error = TRUE; + } + break; + + case MSTATE_DOING_MORE: + /* + * When we are connected, DOING MORE and then go DID + */ + DEBUGASSERT(data->conn); + result = multi_do_more(data, &control); + + if(!result) { + if(control) { + /* if positive, advance to DO_DONE + if negative, go back to DOING */ + multistate(data, control == 1? + MSTATE_DID : MSTATE_DOING); + rc = CURLM_CALL_MULTI_PERFORM; + } + /* else + stay in DO_MORE */ + } + else { + /* failure detected */ + Curl_posttransfer(data); + multi_done(data, result, FALSE); + stream_error = TRUE; + } + break; + + case MSTATE_DID: + DEBUGASSERT(data->conn); + if(data->conn->bits.multiplex) + /* Check if we can move pending requests to send pipe */ + process_pending_handles(multi); /* multiplexed */ + + /* Only perform the transfer if there's a good socket to work with. + Having both BAD is a signal to skip immediately to DONE */ + if((data->conn->sockfd != CURL_SOCKET_BAD) || + (data->conn->writesockfd != CURL_SOCKET_BAD)) + multistate(data, MSTATE_PERFORMING); + else { +#ifndef CURL_DISABLE_FTP + if(data->state.wildcardmatch && + ((data->conn->handler->flags & PROTOPT_WILDCARD) == 0)) { + data->wildcard->state = CURLWC_DONE; + } +#endif + multistate(data, MSTATE_DONE); + } + rc = CURLM_CALL_MULTI_PERFORM; + break; + + case MSTATE_RATELIMITING: /* limit-rate exceeded in either direction */ + DEBUGASSERT(data->conn); + /* if both rates are within spec, resume transfer */ + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, *nowp); + + if(result) { + if(!(data->conn->handler->flags & PROTOPT_DUAL) && + result != CURLE_HTTP2_STREAM) + streamclose(data->conn, "Transfer returned error"); + + Curl_posttransfer(data); + multi_done(data, result, TRUE); + } + else { + send_timeout_ms = 0; + if(data->set.max_send_speed) + send_timeout_ms = + Curl_pgrsLimitWaitTime(data->progress.uploaded, + data->progress.ul_limit_size, + data->set.max_send_speed, + data->progress.ul_limit_start, + *nowp); + + recv_timeout_ms = 0; + if(data->set.max_recv_speed) + recv_timeout_ms = + Curl_pgrsLimitWaitTime(data->progress.downloaded, + data->progress.dl_limit_size, + data->set.max_recv_speed, + data->progress.dl_limit_start, + *nowp); + + if(!send_timeout_ms && !recv_timeout_ms) { + multistate(data, MSTATE_PERFORMING); + Curl_ratelimit(data, *nowp); + } + else if(send_timeout_ms >= recv_timeout_ms) + Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); + else + Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); + } + break; + + case MSTATE_PERFORMING: + { + char *newurl = NULL; + bool retry = FALSE; + bool comeback = FALSE; + DEBUGASSERT(data->state.buffer); + /* check if over send speed */ + send_timeout_ms = 0; + if(data->set.max_send_speed) + send_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.uploaded, + data->progress.ul_limit_size, + data->set.max_send_speed, + data->progress.ul_limit_start, + *nowp); + + /* check if over recv speed */ + recv_timeout_ms = 0; + if(data->set.max_recv_speed) + recv_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.downloaded, + data->progress.dl_limit_size, + data->set.max_recv_speed, + data->progress.dl_limit_start, + *nowp); + + if(send_timeout_ms || recv_timeout_ms) { + Curl_ratelimit(data, *nowp); + multistate(data, MSTATE_RATELIMITING); + if(send_timeout_ms >= recv_timeout_ms) + Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); + else + Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); + break; + } + + /* read/write data if it is ready to do so */ + result = Curl_readwrite(data->conn, data, &done, &comeback); + + if(done || (result == CURLE_RECV_ERROR)) { + /* If CURLE_RECV_ERROR happens early enough, we assume it was a race + * condition and the server closed the reused connection exactly when + * we wanted to use it, so figure out if that is indeed the case. + */ + CURLcode ret = Curl_retry_request(data, &newurl); + if(!ret) + retry = (newurl)?TRUE:FALSE; + else if(!result) + result = ret; + + if(retry) { + /* if we are to retry, set the result to OK and consider the + request as done */ + result = CURLE_OK; + done = TRUE; + } + } + else if((CURLE_HTTP2_STREAM == result) && + Curl_h2_http_1_1_error(data)) { + CURLcode ret = Curl_retry_request(data, &newurl); + + if(!ret) { + infof(data, "Downgrades to HTTP/1.1"); + streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1"); + data->state.httpwant = CURL_HTTP_VERSION_1_1; + /* clear the error message bit too as we ignore the one we got */ + data->state.errorbuf = FALSE; + if(!newurl) + /* typically for HTTP_1_1_REQUIRED error on first flight */ + newurl = strdup(data->state.url); + /* if we are to retry, set the result to OK and consider the request + as done */ + retry = TRUE; + result = CURLE_OK; + done = TRUE; + } + else + result = ret; + } + + if(result) { + /* + * The transfer phase returned error, we mark the connection to get + * closed to prevent being reused. This is because we can't possibly + * know if the connection is in a good shape or not now. Unless it is + * a protocol which uses two "channels" like FTP, as then the error + * happened in the data connection. + */ + + if(!(data->conn->handler->flags & PROTOPT_DUAL) && + result != CURLE_HTTP2_STREAM) + streamclose(data->conn, "Transfer returned error"); + + Curl_posttransfer(data); + multi_done(data, result, TRUE); + } + else if(done) { + + /* call this even if the readwrite function returned error */ + Curl_posttransfer(data); + + /* When we follow redirects or is set to retry the connection, we must + to go back to the CONNECT state */ + if(data->req.newurl || retry) { + followtype follow = FOLLOW_NONE; + if(!retry) { + /* if the URL is a follow-location and not just a retried request + then figure out the URL here */ + free(newurl); + newurl = data->req.newurl; + data->req.newurl = NULL; + follow = FOLLOW_REDIR; + } + else + follow = FOLLOW_RETRY; + (void)multi_done(data, CURLE_OK, FALSE); + /* multi_done() might return CURLE_GOT_NOTHING */ + result = Curl_follow(data, newurl, follow); + if(!result) { + multistate(data, MSTATE_CONNECT); + rc = CURLM_CALL_MULTI_PERFORM; + } + free(newurl); + } + else { + /* after the transfer is done, go DONE */ + + /* but first check to see if we got a location info even though we're + not following redirects */ + if(data->req.location) { + free(newurl); + newurl = data->req.location; + data->req.location = NULL; + result = Curl_follow(data, newurl, FOLLOW_FAKE); + free(newurl); + if(result) { + stream_error = TRUE; + result = multi_done(data, result, TRUE); + } + } + + if(!result) { + multistate(data, MSTATE_DONE); + rc = CURLM_CALL_MULTI_PERFORM; + } + } + } + else if(comeback) { + /* This avoids CURLM_CALL_MULTI_PERFORM so that a very fast transfer + won't get stuck on this transfer at the expense of other concurrent + transfers */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + break; + } + + case MSTATE_DONE: + /* this state is highly transient, so run another loop after this */ + rc = CURLM_CALL_MULTI_PERFORM; + + if(data->conn) { + CURLcode res; + + if(data->conn->bits.multiplex) + /* Check if we can move pending requests to connection */ + process_pending_handles(multi); /* multiplexing */ + + /* post-transfer command */ + res = multi_done(data, result, FALSE); + + /* allow a previously set error code take precedence */ + if(!result) + result = res; + } + +#ifndef CURL_DISABLE_FTP + if(data->state.wildcardmatch) { + if(data->wildcard->state != CURLWC_DONE) { + /* if a wildcard is set and we are not ending -> lets start again + with MSTATE_INIT */ + multistate(data, MSTATE_INIT); + break; + } + } +#endif + /* after we have DONE what we're supposed to do, go COMPLETED, and + it doesn't matter what the multi_done() returned! */ + multistate(data, MSTATE_COMPLETED); + break; + + case MSTATE_COMPLETED: + break; + + case MSTATE_PENDING: + case MSTATE_MSGSENT: + /* handles in these states should NOT be in this list */ + DEBUGASSERT(0); + break; + + default: + return CURLM_INTERNAL_ERROR; + } + + if(data->conn && + data->mstate >= MSTATE_CONNECT && + data->mstate < MSTATE_DO && + rc != CURLM_CALL_MULTI_PERFORM && + !multi_ischanged(multi, false)) { + /* We now handle stream timeouts if and only if this will be the last + * loop iteration. We only check this on the last iteration to ensure + * that if we know we have additional work to do immediately + * (i.e. CURLM_CALL_MULTI_PERFORM == TRUE) then we should do that before + * declaring the connection timed out as we may almost have a completed + * connection. */ + multi_handle_timeout(data, nowp, &stream_error, &result, TRUE); + } + +statemachine_end: + + if(data->mstate < MSTATE_COMPLETED) { + if(result) { + /* + * If an error was returned, and we aren't in completed state now, + * then we go to completed and consider this transfer aborted. + */ + + /* NOTE: no attempt to disconnect connections must be made + in the case blocks above - cleanup happens only here */ + + /* Check if we can move pending requests to send pipe */ + process_pending_handles(multi); /* connection */ + + if(data->conn) { + if(stream_error) { + /* Don't attempt to send data over a connection that timed out */ + bool dead_connection = result == CURLE_OPERATION_TIMEDOUT; + struct connectdata *conn = data->conn; + + /* This is where we make sure that the conn pointer is reset. + We don't have to do this in every case block above where a + failure is detected */ + Curl_detach_connection(data); + + /* remove connection from cache */ + Curl_conncache_remove_conn(data, conn, TRUE); + + /* disconnect properly */ + Curl_disconnect(data, conn, dead_connection); + } + } + else if(data->mstate == MSTATE_CONNECT) { + /* Curl_connect() failed */ + (void)Curl_posttransfer(data); + } + + multistate(data, MSTATE_COMPLETED); + rc = CURLM_CALL_MULTI_PERFORM; + } + /* if there's still a connection to use, call the progress function */ + else if(data->conn && Curl_pgrsUpdate(data)) { + /* aborted due to progress callback return code must close the + connection */ + result = CURLE_ABORTED_BY_CALLBACK; + streamclose(data->conn, "Aborted by callback"); + + /* if not yet in DONE state, go there, otherwise COMPLETED */ + multistate(data, (data->mstate < MSTATE_DONE)? + MSTATE_DONE: MSTATE_COMPLETED); + rc = CURLM_CALL_MULTI_PERFORM; + } + } + + if(MSTATE_COMPLETED == data->mstate) { + if(data->set.fmultidone) { + /* signal via callback instead */ + data->set.fmultidone(data, result); + } + else { + /* now fill in the Curl_message with this info */ + msg = &data->msg; + + msg->extmsg.msg = CURLMSG_DONE; + msg->extmsg.easy_handle = data; + msg->extmsg.data.result = result; + + multi_addmsg(multi, msg); + DEBUGASSERT(!data->conn); + } + multistate(data, MSTATE_MSGSENT); + + /* add this handle to the list of msgsent handles */ + Curl_llist_insert_next(&multi->msgsent, multi->msgsent.tail, data, + &data->connect_queue); + /* unlink from the main list */ + unlink_easy(multi, data); + return CURLM_OK; + } + } while((rc == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE)); + + data->result = result; + return rc; +} + + +CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) +{ + struct Curl_easy *data; + CURLMcode returncode = CURLM_OK; + struct Curl_tree *t; + struct curltime now = Curl_now(); + + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + data = multi->easyp; + if(data) { + CURLMcode result; + bool nosig = data->set.no_signal; + SIGPIPE_VARIABLE(pipe_st); + sigpipe_ignore(data, &pipe_st); + /* Do the loop and only alter the signal ignore state if the next handle + has a different NO_SIGNAL state than the previous */ + do { + /* the current node might be unlinked in multi_runsingle(), get the next + pointer now */ + struct Curl_easy *datanext = data->next; + if(data->set.no_signal != nosig) { + sigpipe_restore(&pipe_st); + sigpipe_ignore(data, &pipe_st); + nosig = data->set.no_signal; + } + result = multi_runsingle(multi, &now, data); + if(result) + returncode = result; + data = datanext; /* operate on next handle */ + } while(data); + sigpipe_restore(&pipe_st); + } + + /* + * Simply remove all expired timers from the splay since handles are dealt + * with unconditionally by this function and curl_multi_timeout() requires + * that already passed/handled expire times are removed from the splay. + * + * It is important that the 'now' value is set at the entry of this function + * and not for the current time as it may have ticked a little while since + * then and then we risk this loop to remove timers that actually have not + * been handled! + */ + do { + multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); + if(t) + /* the removed may have another timeout in queue */ + (void)add_next_timeout(now, multi, t->payload); + + } while(t); + + *running_handles = multi->num_alive; + + if(CURLM_OK >= returncode) + returncode = Curl_update_timer(multi); + + return returncode; +} + +/* unlink_all_msgsent_handles() detaches all those easy handles from this + multi handle */ +static void unlink_all_msgsent_handles(struct Curl_multi *multi) +{ + struct Curl_llist_element *e = multi->msgsent.head; + if(e) { + struct Curl_easy *data = e->ptr; + DEBUGASSERT(data->mstate == MSTATE_MSGSENT); + data->multi = NULL; + } +} + +CURLMcode curl_multi_cleanup(struct Curl_multi *multi) +{ + struct Curl_easy *data; + struct Curl_easy *nextdata; + + if(GOOD_MULTI_HANDLE(multi)) { + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + multi->magic = 0; /* not good anymore */ + + unlink_all_msgsent_handles(multi); + process_pending_handles(multi); + /* First remove all remaining easy handles */ + data = multi->easyp; + while(data) { + nextdata = data->next; + if(!data->state.done && data->conn) + /* if DONE was never called for this handle */ + (void)multi_done(data, CURLE_OK, TRUE); + if(data->dns.hostcachetype == HCACHE_MULTI) { + /* clear out the usage of the shared DNS cache */ + Curl_hostcache_clean(data, data->dns.hostcache); + data->dns.hostcache = NULL; + data->dns.hostcachetype = HCACHE_NONE; + } + + /* Clear the pointer to the connection cache */ + data->state.conn_cache = NULL; + data->multi = NULL; /* clear the association */ + +#ifdef USE_LIBPSL + if(data->psl == &multi->psl) + data->psl = NULL; +#endif + + data = nextdata; + } + + /* Close all the connections in the connection cache */ + Curl_conncache_close_all_connections(&multi->conn_cache); + + sockhash_destroy(&multi->sockhash); + Curl_conncache_destroy(&multi->conn_cache); + Curl_hash_destroy(&multi->hostcache); + Curl_psl_destroy(&multi->psl); + +#ifdef USE_WINSOCK + WSACloseEvent(multi->wsa_event); +#else +#ifdef ENABLE_WAKEUP + wakeup_close(multi->wakeup_pair[0]); + wakeup_close(multi->wakeup_pair[1]); +#endif +#endif + +#ifdef USE_SSL + Curl_free_multi_ssl_backend_data(multi->ssl_backend_data); +#endif + + free(multi); + + return CURLM_OK; + } + return CURLM_BAD_HANDLE; +} + +/* + * curl_multi_info_read() + * + * This function is the primary way for a multi/multi_socket application to + * figure out if a transfer has ended. We MUST make this function as fast as + * possible as it will be polled frequently and we MUST NOT scan any lists in + * here to figure out things. We must scale fine to thousands of handles and + * beyond. The current design is fully O(1). + */ + +CURLMsg *curl_multi_info_read(struct Curl_multi *multi, int *msgs_in_queue) +{ + struct Curl_message *msg; + + *msgs_in_queue = 0; /* default to none */ + + 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_element *e; + + /* extract the head of the list to return */ + e = multi->msglist.head; + + msg = e->ptr; + + /* remove the extracted entry */ + Curl_llist_remove(&multi->msglist, e, NULL); + + *msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist)); + + return &msg->extmsg; + } + return NULL; +} + +/* + * singlesocket() checks what sockets we deal with and their "action state" + * and if we have a different state in any of those sockets from last time we + * call the callback accordingly. + */ +static CURLMcode singlesocket(struct Curl_multi *multi, + struct Curl_easy *data) +{ + struct easy_pollset cur_poll; + unsigned int i; + struct Curl_sh_entry *entry; + curl_socket_t s; + int rc; + + /* Fill in the 'current' struct with the state as it is now: what sockets to + supervise and for what actions */ + multi_getsock(data, &cur_poll); + + /* We have 0 .. N sockets already and we get to know about the 0 .. M + sockets we should have from now on. Detect the differences, remove no + longer supervised ones and add new ones */ + + /* walk over the sockets we got right now */ + for(i = 0; i < cur_poll.num; i++) { + unsigned char cur_action = cur_poll.actions[i]; + unsigned char last_action = 0; + int comboaction; + + s = cur_poll.sockets[i]; + + /* get it from the hash */ + entry = sh_getentry(&multi->sockhash, s); + if(entry) { + /* check if new for this transfer */ + unsigned int j; + for(j = 0; j< data->last_poll.num; j++) { + if(s == data->last_poll.sockets[j]) { + last_action = data->last_poll.actions[j]; + break; + } + } + } + else { + /* this is a socket we didn't have before, add it to the hash! */ + entry = sh_addentry(&multi->sockhash, s); + if(!entry) + /* fatal */ + return CURLM_OUT_OF_MEMORY; + } + if(last_action && (last_action != cur_action)) { + /* Socket was used already, but different action now */ + if(last_action & CURL_POLL_IN) + entry->readers--; + if(last_action & CURL_POLL_OUT) + entry->writers--; + if(cur_action & CURL_POLL_IN) + entry->readers++; + if(cur_action & CURL_POLL_OUT) + entry->writers++; + } + else if(!last_action) { + /* a new transfer using this socket */ + entry->users++; + if(cur_action & CURL_POLL_IN) + entry->readers++; + if(cur_action & CURL_POLL_OUT) + entry->writers++; + + /* add 'data' to the transfer hash on this socket! */ + if(!Curl_hash_add(&entry->transfers, (char *)&data, /* hash key */ + sizeof(struct Curl_easy *), data)) { + Curl_hash_destroy(&entry->transfers); + return CURLM_OUT_OF_MEMORY; + } + } + + comboaction = (entry->writers ? CURL_POLL_OUT : 0) | + (entry->readers ? CURL_POLL_IN : 0); + + /* socket existed before and has the same action set as before */ + if(last_action && ((int)entry->action == comboaction)) + /* same, continue */ + continue; + + if(multi->socket_cb) { + set_in_callback(multi, TRUE); + rc = multi->socket_cb(data, s, comboaction, multi->socket_userp, + entry->socketp); + + set_in_callback(multi, FALSE); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + } + + entry->action = comboaction; /* store the current action state */ + } + + /* Check for last_poll.sockets that no longer appear in cur_poll.sockets. + * Need to remove the easy handle from the multi->sockhash->transfers and + * remove multi->sockhash entry when this was the last transfer */ + for(i = 0; i< data->last_poll.num; i++) { + unsigned int j; + bool stillused = FALSE; + s = data->last_poll.sockets[i]; + for(j = 0; j < cur_poll.num; j++) { + if(s == cur_poll.sockets[j]) { + /* this is still supervised */ + stillused = TRUE; + break; + } + } + if(stillused) + continue; + + entry = sh_getentry(&multi->sockhash, s); + /* if this is NULL here, the socket has been closed and notified so + already by Curl_multi_closed() */ + if(entry) { + unsigned char oldactions = data->last_poll.actions[i]; + /* this socket has been removed. Decrease user count */ + entry->users--; + if(oldactions & CURL_POLL_OUT) + entry->writers--; + if(oldactions & CURL_POLL_IN) + entry->readers--; + if(!entry->users) { + if(multi->socket_cb) { + set_in_callback(multi, TRUE); + rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, + multi->socket_userp, entry->socketp); + set_in_callback(multi, FALSE); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + } + sh_delentry(entry, &multi->sockhash, s); + } + else { + /* still users, but remove this handle as a user of this socket */ + if(Curl_hash_delete(&entry->transfers, (char *)&data, + sizeof(struct Curl_easy *))) { + DEBUGASSERT(NULL); + } + } + } + } /* for loop over num */ + + /* Remember for next time */ + memcpy(&data->last_poll, &cur_poll, sizeof(data->last_poll)); + return CURLM_OK; +} + +CURLcode Curl_updatesocket(struct Curl_easy *data) +{ + if(singlesocket(data->multi, data)) + return CURLE_ABORTED_BY_CALLBACK; + return CURLE_OK; +} + + +/* + * Curl_multi_closed() + * + * Used by the connect code to tell the multi_socket code that one of the + * sockets we were using is about to be closed. This function will then + * remove it from the sockethash for this handle to make the multi_socket API + * behave properly, especially for the case when libcurl will create another + * socket again and it gets the same file descriptor number. + */ + +void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s) +{ + if(data) { + /* if there's still an easy handle associated with this connection */ + struct Curl_multi *multi = data->multi; + if(multi) { + /* this is set if this connection is part of a handle that is added to + a multi handle, and only then this is necessary */ + struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s); + + if(entry) { + int rc = 0; + if(multi->socket_cb) { + set_in_callback(multi, TRUE); + rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, + multi->socket_userp, entry->socketp); + set_in_callback(multi, FALSE); + } + + /* now remove it from the socket hash */ + sh_delentry(entry, &multi->sockhash, s); + if(rc == -1) + /* This just marks the multi handle as "dead" without returning an + error code primarily because this function is used from many + places where propagating an error back is tricky. */ + multi->dead = TRUE; + } + } + } +} + +/* + * add_next_timeout() + * + * Each Curl_easy has a list of timeouts. The add_next_timeout() is called + * when it has just 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 + * timeout is used to sort it on. + */ +static CURLMcode add_next_timeout(struct curltime now, + struct Curl_multi *multi, + struct Curl_easy *d) +{ + struct curltime *tv = &d->state.expiretime; + struct Curl_llist *list = &d->state.timeoutlist; + struct Curl_llist_element *e; + struct time_node *node = NULL; + + /* 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 = list->head; e;) { + struct Curl_llist_element *n = e->next; + timediff_t diff; + node = (struct time_node *)e->ptr; + diff = Curl_timediff_us(node->time, now); + if(diff <= 0) + /* remove outdated entry */ + Curl_llist_remove(list, e, NULL); + else + /* the list is sorted so get out on the first mismatch */ + break; + e = n; + } + e = list->head; + 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 { + /* copy the first entry to 'tv' */ + memcpy(tv, &node->time, sizeof(*tv)); + + /* 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); + } + return CURLM_OK; +} + +static CURLMcode multi_socket(struct Curl_multi *multi, + bool checkall, + curl_socket_t s, + int ev_bitmask, + int *running_handles) +{ + CURLMcode result = CURLM_OK; + struct Curl_easy *data = NULL; + struct Curl_tree *t; + struct curltime now = Curl_now(); + bool first = FALSE; + bool nosig = FALSE; + SIGPIPE_VARIABLE(pipe_st); + + if(checkall) { + /* *perform() deals with running_handles on its own */ + result = curl_multi_perform(multi, running_handles); + + /* walk through each easy handle and do the socket state change magic + and callbacks */ + if(result != CURLM_BAD_HANDLE) { + data = multi->easyp; + while(data && !result) { + result = singlesocket(multi, data); + data = data->next; + } + } + + /* or should we fall-through and do the timer-based stuff? */ + return result; + } + if(s != CURL_SOCKET_TIMEOUT) { + struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s); + + if(!entry) + /* Unmatched socket, we can't act on it but we ignore this fact. In + real-world tests it has been proved that libevent can in fact give + the application actions even though the socket was just previously + asked to get removed, so thus we better survive stray socket actions + and just move on. */ + ; + else { + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + + /* the socket can be shared by many transfers, iterate */ + Curl_hash_start_iterate(&entry->transfers, &iter); + for(he = Curl_hash_next_element(&iter); he; + he = Curl_hash_next_element(&iter)) { + data = (struct Curl_easy *)he->ptr; + DEBUGASSERT(data); + DEBUGASSERT(data->magic == CURLEASY_MAGIC_NUMBER); + + if(data->conn && !(data->conn->handler->flags & PROTOPT_DIRLOCK)) + /* set socket event bitmask if they're not locked */ + data->conn->cselect_bits = (unsigned char)ev_bitmask; + + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + + /* Now we fall-through and do the timer-based stuff, since we don't want + to force the user to have to deal with timeouts as long as at least + one connection in fact has traffic. */ + + data = NULL; /* set data to NULL again to avoid calling + multi_runsingle() in case there's no need to */ + now = Curl_now(); /* get a newer time since the multi_runsingle() loop + may have taken some time */ + } + } + else { + /* Asked to run due to time-out. Clear the 'lastcall' variable to force + Curl_update_timer() to trigger a callback to the app again even 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->timer_lastcall, 0, sizeof(multi->timer_lastcall)); + } + + /* + * The loop following here will go on as long as there are expire-times left + * to process in the splay and 'data' will be re-assigned for every expired + * handle we deal with. + */ + do { + /* the first loop lap 'data' can be NULL */ + if(data) { + if(!first) { + first = TRUE; + nosig = data->set.no_signal; /* initial state */ + sigpipe_ignore(data, &pipe_st); + } + else if(data->set.no_signal != nosig) { + sigpipe_restore(&pipe_st); + sigpipe_ignore(data, &pipe_st); + nosig = data->set.no_signal; /* remember new state */ + } + result = multi_runsingle(multi, &now, data); + + if(CURLM_OK >= result) { + /* get the socket(s) and check if the state has been changed since + last */ + result = singlesocket(multi, data); + if(result) + break; + } + } + + /* Check if there's one (more) expired timer to deal with! This function + extracts a matching node if there is one */ + + multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); + if(t) { + data = t->payload; /* assign this for next loop */ + (void)add_next_timeout(now, multi, t->payload); + } + + } while(t); + if(first) + sigpipe_restore(&pipe_st); + + *running_handles = multi->num_alive; + return result; +} + +#undef curl_multi_setopt +CURLMcode curl_multi_setopt(struct Curl_multi *multi, + CURLMoption option, ...) +{ + CURLMcode res = CURLM_OK; + va_list param; + unsigned long uarg; + + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + 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: + multi->max_host_connections = va_arg(param, long); + break; + case CURLMOPT_MAX_TOTAL_CONNECTIONS: + multi->max_total_connections = va_arg(param, long); + 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; + default: + res = CURLM_UNKNOWN_OPTION; + break; + } + va_end(param); + return res; +} + +/* we define curl_multi_socket() in the public multi.h header */ +#undef curl_multi_socket + +CURLMcode curl_multi_socket(struct Curl_multi *multi, curl_socket_t s, + int *running_handles) +{ + CURLMcode result; + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + result = multi_socket(multi, FALSE, s, 0, running_handles); + if(CURLM_OK >= result) + result = Curl_update_timer(multi); + return result; +} + +CURLMcode curl_multi_socket_action(struct Curl_multi *multi, curl_socket_t s, + int ev_bitmask, int *running_handles) +{ + CURLMcode result; + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + result = multi_socket(multi, FALSE, s, ev_bitmask, running_handles); + if(CURLM_OK >= result) + result = Curl_update_timer(multi); + return result; +} + +CURLMcode curl_multi_socket_all(struct Curl_multi *multi, int *running_handles) +{ + CURLMcode result; + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + result = multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles); + if(CURLM_OK >= result) + result = Curl_update_timer(multi); + return result; +} + +static CURLMcode multi_timeout(struct Curl_multi *multi, + long *timeout_ms) +{ + static const struct curltime tv_zero = {0, 0}; + + if(multi->dead) { + *timeout_ms = 0; + return CURLM_OK; + } + + if(multi->timetree) { + /* we have a tree of expire times */ + struct curltime now = Curl_now(); + + /* splay the lowest to the bottom */ + multi->timetree = Curl_splay(tv_zero, multi->timetree); + + if(Curl_splaycomparekeys(multi->timetree->key, now) > 0) { + /* some time left before expiration */ + timediff_t diff = Curl_timediff_ceil(multi->timetree->key, now); + /* this should be safe even on 32 bit archs, as we don't use that + overly long timeouts */ + *timeout_ms = (long)diff; + } + else + /* 0 means immediately */ + *timeout_ms = 0; + } + else + *timeout_ms = -1; + + return CURLM_OK; +} + +CURLMcode curl_multi_timeout(struct Curl_multi *multi, + long *timeout_ms) +{ + /* First, make some basic checks that the CURLM handle is a good handle */ + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + return multi_timeout(multi, timeout_ms); +} + +/* + * Tell the application it should update its timers, if it subscribes to the + * update timer callback. + */ +CURLMcode Curl_update_timer(struct Curl_multi *multi) +{ + long timeout_ms; + int rc; + + if(!multi->timer_cb || multi->dead) + return CURLM_OK; + if(multi_timeout(multi, &timeout_ms)) { + return CURLM_OK; + } + if(timeout_ms < 0) { + static const struct curltime none = {0, 0}; + if(Curl_splaycomparekeys(none, multi->timer_lastcall)) { + multi->timer_lastcall = none; + /* there's no timeout now but there was one previously, tell the app to + disable it */ + set_in_callback(multi, TRUE); + rc = multi->timer_cb(multi, -1, multi->timer_userp); + set_in_callback(multi, FALSE); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + return CURLM_OK; + } + return CURLM_OK; + } + + /* When multi_timeout() is done, multi->timetree points to the node with the + * timeout we got the (relative) time-out time for. We can thus easily check + * if this is the same (fixed) time as we got in a previous call and then + * avoid calling the callback again. */ + if(Curl_splaycomparekeys(multi->timetree->key, multi->timer_lastcall) == 0) + return CURLM_OK; + + multi->timer_lastcall = multi->timetree->key; + + set_in_callback(multi, TRUE); + rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp); + set_in_callback(multi, FALSE); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + return CURLM_OK; +} + +/* + * multi_deltimeout() + * + * Remove a given timestamp from the list of timeouts. + */ +static void +multi_deltimeout(struct Curl_easy *data, expire_id eid) +{ + struct Curl_llist_element *e; + struct Curl_llist *timeoutlist = &data->state.timeoutlist; + /* find and remove the specific node from the list */ + for(e = timeoutlist->head; e; e = e->next) { + struct time_node *n = (struct time_node *)e->ptr; + if(n->eid == eid) { + Curl_llist_remove(timeoutlist, e, NULL); + return; + } + } +} + +/* + * 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) +{ + struct Curl_llist_element *e; + struct time_node *node; + struct Curl_llist_element *prev = NULL; + size_t n; + struct Curl_llist *timeoutlist = &data->state.timeoutlist; + + 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 = timeoutlist->head; e; e = e->next) { + struct time_node *check = (struct time_node *)e->ptr; + timediff_t diff = Curl_timediff(check->time, node->time); + if(diff > 0) + break; + prev = e; + } + + } + /* else + this is the first timeout on the list */ + + Curl_llist_insert_next(timeoutlist, prev, node, &node->list); + return CURLM_OK; +} + +/* + * 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() + * + * The timeout will be added to a queue of timeouts if it defines a moment in + * time that is later than the current head of queue. + * + * 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) +{ + struct Curl_multi *multi = data->multi; + struct curltime *nowp = &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_now(); + set.tv_sec += (time_t)(milli/1000); /* might be a 64 to 32 bit conversion */ + set.tv_usec += (unsigned int)(milli%1000)*1000; + + if(set.tv_usec >= 1000000) { + set.tv_sec++; + set.tv_usec -= 1000000; + } + + /* Remove any timer with the same id just in case. */ + 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(nowp->tv_sec || nowp->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 = Curl_timediff(set, *nowp); + int rc; + + if(diff > 0) { + /* The current splay tree entry is sooner than this new expiry time. + We don't 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. */ + *nowp = set; + data->state.timenode.payload = data; + multi->timetree = Curl_splayinsert(*nowp, multi->timetree, + &data->state.timenode); +} + +/* + * Curl_expire_done() + * + * Removes the expire timer. Marks it as done. + * + */ +void Curl_expire_done(struct Curl_easy *data, expire_id id) +{ + /* remove the timer, if there */ + multi_deltimeout(data, id); +} + +/* + * Curl_expire_clear() + * + * Clear ALL timeout values for this handle. + */ +void Curl_expire_clear(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) { + /* 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); + + /* flush the timeout list too */ + while(list->size > 0) { + Curl_llist_remove(list, list->tail, NULL); + } + +#ifdef DEBUGBUILD + infof(data, "Expire cleared"); +#endif + nowp->tv_sec = 0; + nowp->tv_usec = 0; + } +} + + + + +CURLMcode curl_multi_assign(struct Curl_multi *multi, curl_socket_t s, + void *hashp) +{ + struct Curl_sh_entry *there = NULL; + + there = sh_getentry(&multi->sockhash, s); + + if(!there) + return CURLM_BAD_SOCKET; + + there->socketp = hashp; + + return CURLM_OK; +} + +size_t Curl_multi_max_host_connections(struct Curl_multi *multi) +{ + return multi ? multi->max_host_connections : 0; +} + +size_t Curl_multi_max_total_connections(struct Curl_multi *multi) +{ + return multi ? multi->max_total_connections : 0; +} + +/* + * When information about a connection has appeared, call this! + */ + +void Curl_multiuse_state(struct Curl_easy *data, + int bundlestate) /* use BUNDLE_* defines */ +{ + struct connectdata *conn; + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + conn = data->conn; + DEBUGASSERT(conn); + DEBUGASSERT(conn->bundle); + + conn->bundle->multiuse = bundlestate; + process_pending_handles(data->multi); +} + +/* process_pending_handles() moves all handles from PENDING + back into the main list and change state to CONNECT */ +static void process_pending_handles(struct Curl_multi *multi) +{ + struct Curl_llist_element *e = multi->pending.head; + if(e) { + struct Curl_easy *data = e->ptr; + + DEBUGASSERT(data->mstate == MSTATE_PENDING); + + /* put it back into the main list */ + link_easy(multi, data); + + multistate(data, MSTATE_CONNECT); + + /* Remove this node from the list */ + Curl_llist_remove(&multi->pending, e, NULL); + + /* Make sure that the handle will be processed soonish. */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + + /* mark this as having been in the pending queue */ + data->state.previouslypending = TRUE; + } +} + +void Curl_set_in_callback(struct Curl_easy *data, bool value) +{ + /* might get called when there is no data pointer! */ + if(data) { + if(data->multi_easy) + data->multi_easy->in_callback = value; + else if(data->multi) + data->multi->in_callback = value; + } +} + +bool Curl_is_in_callback(struct Curl_easy *easy) +{ + return ((easy->multi && easy->multi->in_callback) || + (easy->multi_easy && easy->multi_easy->in_callback)); +} + +unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) +{ + DEBUGASSERT(multi); + return multi->max_concurrent_streams; +} + +struct Curl_easy **curl_multi_get_handles(struct Curl_multi *multi) +{ + struct Curl_easy **a = malloc(sizeof(struct Curl_easy *) * + (multi->num_easy + 1)); + if(a) { + unsigned int i = 0; + struct Curl_easy *e = multi->easyp; + while(e) { + DEBUGASSERT(i < multi->num_easy); + if(!e->state.internal) + a[i++] = e; + e = e->next; + } + a[i] = NULL; /* last entry is a NULL */ + } + return a; +} diff --git a/Utilities/cmcurl/lib/multihandle.h b/Utilities/cmcurl/lib/multihandle.h new file mode 100644 index 0000000..e03e382 --- /dev/null +++ b/Utilities/cmcurl/lib/multihandle.h @@ -0,0 +1,179 @@ +#ifndef HEADER_CURL_MULTIHANDLE_H +#define HEADER_CURL_MULTIHANDLE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "llist.h" +#include "hash.h" +#include "conncache.h" +#include "psl.h" +#include "socketpair.h" + +struct connectdata; + +struct Curl_message { + struct Curl_llist_element list; + /* the 'CURLMsg' is the part that is visible to the external user */ + struct CURLMsg extmsg; +}; + +/* NOTE: if you add a state here, add the name to the statename[] array as + well! +*/ +typedef enum { + MSTATE_INIT, /* 0 - start in this state */ + MSTATE_PENDING, /* 1 - no connections, waiting for one */ + MSTATE_CONNECT, /* 2 - resolve/connect has been sent off */ + MSTATE_RESOLVING, /* 3 - awaiting the resolve to finalize */ + MSTATE_CONNECTING, /* 4 - awaiting the TCP connect to finalize */ + MSTATE_TUNNELING, /* 5 - awaiting HTTPS proxy SSL initialization to + complete and/or proxy CONNECT to finalize */ + MSTATE_PROTOCONNECT, /* 6 - initiate protocol connect procedure */ + MSTATE_PROTOCONNECTING, /* 7 - completing the protocol-specific connect + phase */ + MSTATE_DO, /* 8 - start send off the request (part 1) */ + MSTATE_DOING, /* 9 - sending off the request (part 1) */ + MSTATE_DOING_MORE, /* 10 - send off the request (part 2) */ + MSTATE_DID, /* 11 - done sending off request */ + MSTATE_PERFORMING, /* 12 - transfer data */ + MSTATE_RATELIMITING, /* 13 - wait because limit-rate exceeded */ + MSTATE_DONE, /* 14 - post data transfer operation */ + MSTATE_COMPLETED, /* 15 - operation complete */ + MSTATE_MSGSENT, /* 16 - the operation complete message is sent */ + MSTATE_LAST /* 17 - not a true state, never use this */ +} CURLMstate; + +/* we support N sockets per easy handle. Set the corresponding bit to what + action we should wait for */ +#define MAX_SOCKSPEREASYHANDLE 5 +#define GETSOCK_READABLE (0x00ff) +#define GETSOCK_WRITABLE (0xff00) + +#define CURLPIPE_ANY (CURLPIPE_MULTIPLEX) + +#if !defined(CURL_DISABLE_SOCKETPAIR) +#define ENABLE_WAKEUP +#endif + +/* value for MAXIMUM CONCURRENT STREAMS upper limit */ +#define INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1) + +/* Curl_multi SSL backend-specific data; declared differently by each SSL + backend */ +struct multi_ssl_backend_data; + +/* 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; + + /* We have a doubly-linked list with easy handles */ + struct Curl_easy *easyp; + struct Curl_easy *easylp; /* last node */ + + unsigned int num_easy; /* amount of entries in the linked list above. */ + unsigned int num_alive; /* amount of easy handles that are added but have + not yet reached COMPLETE state */ + + struct Curl_llist msglist; /* a list of messages from completed transfers */ + + struct Curl_llist pending; /* Curl_easys that are in the + MSTATE_PENDING state */ + struct Curl_llist msgsent; /* Curl_easys that are in the + MSTATE_MSGSENT state */ + + /* callback function and user data pointer for the *socket() API */ + curl_socket_callback socket_cb; + void *socket_userp; + + /* callback function and user data pointer for server push */ + curl_push_callback push_cb; + void *push_userp; + + /* Hostname cache */ + struct Curl_hash hostcache; + +#ifdef USE_LIBPSL + /* PSL cache. */ + struct PslCache psl; +#endif + + /* timetree points to the splay-tree of time nodes to figure out expire + times of all currently set timers */ + struct Curl_tree *timetree; + +#if defined(USE_SSL) + struct multi_ssl_backend_data *ssl_backend_data; +#endif + + /* 'sockhash' is the lookup hash for socket descriptor => easy handles (note + the pluralis form, there can be more than one easy handle waiting on the + same actual socket) */ + struct Curl_hash sockhash; + + /* Shared connection cache (bundles)*/ + struct conncache conn_cache; + + long max_host_connections; /* if >0, a fixed limit of the maximum number + of connections per host */ + + long max_total_connections; /* if >0, a fixed limit of the maximum number + of connections in total */ + + /* timer callback and user data pointer for the *socket() API */ + curl_multi_timer_callback timer_cb; + void *timer_userp; + struct curltime timer_lastcall; /* the fixed time for the timeout for the + previous callback */ +#ifdef USE_WINSOCK + WSAEVENT wsa_event; /* winsock event used for waits */ +#else +#ifdef ENABLE_WAKEUP + curl_socket_t wakeup_pair[2]; /* socketpair() used for wakeup + 0 is used for read, 1 is used for write */ +#endif +#endif + unsigned int max_concurrent_streams; + unsigned int maxconnects; /* if >0, a fixed limit of the maximum number of + entries we're allowed to grow the connection + cache to */ +#define IPV6_UNKNOWN 0 +#define IPV6_DEAD 1 +#define IPV6_WORKS 2 + unsigned char ipv6_up; /* IPV6_* defined */ + BIT(multiplexing); /* multiplexing wanted */ + BIT(recheckstate); /* see Curl_multi_connchanged */ + BIT(in_callback); /* true while executing a callback */ +#ifdef USE_OPENSSL + BIT(ssl_seeded); +#endif + BIT(dead); /* a callback returned error, everything needs to crash and + burn */ +#ifdef DEBUGBUILD + BIT(warned); /* true after user warned of DEBUGBUILD */ +#endif +}; + +#endif /* HEADER_CURL_MULTIHANDLE_H */ diff --git a/Utilities/cmcurl/lib/multiif.h b/Utilities/cmcurl/lib/multiif.h new file mode 100644 index 0000000..7a344fa --- /dev/null +++ b/Utilities/cmcurl/lib/multiif.h @@ -0,0 +1,97 @@ +#ifndef HEADER_CURL_MULTIIF_H +#define HEADER_CURL_MULTIIF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Prototypes for library-wide functions provided by multi.c + */ + +CURLcode Curl_updatesocket(struct Curl_easy *data); +void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id); +void Curl_expire_clear(struct Curl_easy *data); +void Curl_expire_done(struct Curl_easy *data, expire_id id); +CURLMcode Curl_update_timer(struct Curl_multi *multi) WARN_UNUSED_RESULT; +void Curl_attach_connection(struct Curl_easy *data, + struct connectdata *conn); +void Curl_detach_connection(struct Curl_easy *data); +bool Curl_multiplex_wanted(const struct Curl_multi *multi); +void Curl_set_in_callback(struct Curl_easy *data, bool value); +bool Curl_is_in_callback(struct Curl_easy *easy); +CURLcode Curl_preconnect(struct Curl_easy *data); + +void Curl_multi_connchanged(struct Curl_multi *multi); + +/* Internal version of curl_multi_init() accepts size parameters for the + socket, connection and dns hashes */ +struct Curl_multi *Curl_multi_handle(int hashsize, int chashsize, + int dnssize); + +/* the write bits start at bit 16 for the *getsock() bitmap */ +#define GETSOCK_WRITEBITSTART 16 + +#define GETSOCK_BLANK 0 /* no bits set */ + +/* set the bit for the given sock number to make the bitmap for writable */ +#define GETSOCK_WRITESOCK(x) (1 << (GETSOCK_WRITEBITSTART + (x))) + +/* set the bit for the given sock number to make the bitmap for readable */ +#define GETSOCK_READSOCK(x) (1 << (x)) + +/* mask for checking if read and/or write is set for index x */ +#define GETSOCK_MASK_RW(x) (GETSOCK_READSOCK(x)|GETSOCK_WRITESOCK(x)) + +/* Return the value of the CURLMOPT_MAX_HOST_CONNECTIONS option */ +size_t Curl_multi_max_host_connections(struct Curl_multi *multi); + +/* Return the value of the CURLMOPT_MAX_TOTAL_CONNECTIONS option */ +size_t Curl_multi_max_total_connections(struct Curl_multi *multi); + +void Curl_multiuse_state(struct Curl_easy *data, + int bundlestate); /* use BUNDLE_* defines */ + +/* + * Curl_multi_closed() + * + * Used by the connect code to tell the multi_socket code that one of the + * sockets we were using is about to be closed. This function will then + * remove it from the sockethash for this handle to make the multi_socket API + * behave properly, especially for the case when libcurl will create another + * socket again and it gets the same file descriptor number. + */ + +void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s); + +/* + * Add a handle and move it into PERFORM state at once. For pushed streams. + */ +CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn); + + +/* Return the value of the CURLMOPT_MAX_CONCURRENT_STREAMS option */ +unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi); + +#endif /* HEADER_CURL_MULTIIF_H */ diff --git a/Utilities/cmcurl/lib/netrc.c b/Utilities/cmcurl/lib/netrc.c new file mode 100644 index 0000000..038c6dc --- /dev/null +++ b/Utilities/cmcurl/lib/netrc.c @@ -0,0 +1,349 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#ifndef CURL_DISABLE_NETRC + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#include <curl/curl.h> +#include "netrc.h" +#include "strtok.h" +#include "strcase.h" +#include "curl_get_line.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* Get user and password from .netrc when given a machine name */ + +enum host_lookup_state { + NOTHING, + HOSTFOUND, /* the 'machine' keyword was found */ + HOSTVALID, /* this is "our" machine! */ + MACDEF +}; + +#define NETRC_FILE_MISSING 1 +#define NETRC_FAILED -1 +#define NETRC_SUCCESS 0 + +/* + * Returns zero on success. + */ +static int parsenetrc(const char *host, + char **loginp, + char **passwordp, + char *netrcfile) +{ + FILE *file; + int retcode = NETRC_FILE_MISSING; + char *login = *loginp; + char *password = *passwordp; + bool specific_login = (login && *login != 0); + bool login_alloc = FALSE; + bool password_alloc = FALSE; + enum host_lookup_state state = NOTHING; + + char state_login = 0; /* Found a login keyword */ + char state_password = 0; /* Found a password keyword */ + int state_our_login = TRUE; /* With specific_login, found *our* login + name (or login-less line) */ + + DEBUGASSERT(netrcfile); + + file = fopen(netrcfile, FOPEN_READTEXT); + if(file) { + bool done = FALSE; + char netrcbuffer[4096]; + int netrcbuffsize = (int)sizeof(netrcbuffer); + + while(!done && Curl_get_line(netrcbuffer, netrcbuffsize, file)) { + char *tok; + char *tok_end; + bool quoted; + if(state == MACDEF) { + if((netrcbuffer[0] == '\n') || (netrcbuffer[0] == '\r')) + state = NOTHING; + else + continue; + } + tok = netrcbuffer; + while(tok) { + while(ISBLANK(*tok)) + tok++; + /* tok is first non-space letter */ + if(!*tok || (*tok == '#')) + /* end of line or the rest is a comment */ + break; + + /* leading double-quote means quoted string */ + quoted = (*tok == '\"'); + + tok_end = tok; + if(!quoted) { + while(!ISSPACE(*tok_end)) + tok_end++; + *tok_end = 0; + } + else { + bool escape = FALSE; + bool endquote = FALSE; + char *store = tok; + tok_end++; /* pass the leading quote */ + while(*tok_end) { + char s = *tok_end; + if(escape) { + escape = FALSE; + switch(s) { + case 'n': + s = '\n'; + break; + case 'r': + s = '\r'; + break; + case 't': + s = '\t'; + break; + } + } + else if(s == '\\') { + escape = TRUE; + tok_end++; + continue; + } + else if(s == '\"') { + tok_end++; /* pass the ending quote */ + endquote = TRUE; + break; + } + *store++ = s; + tok_end++; + } + *store = 0; + if(escape || !endquote) { + /* bad syntax, get out */ + retcode = NETRC_FAILED; + goto out; + } + } + + if((login && *login) && (password && *password)) { + done = TRUE; + break; + } + + switch(state) { + case NOTHING: + if(strcasecompare("macdef", tok)) { + /* Define a macro. A macro is defined with the specified name; its + contents begin with the next .netrc line and continue until a + null line (consecutive new-line characters) is encountered. */ + state = MACDEF; + } + else if(strcasecompare("machine", tok)) { + /* the next tok is the machine name, this is in itself the + delimiter that starts the stuff entered for this machine, + after this we need to search for 'login' and + 'password'. */ + state = HOSTFOUND; + } + else if(strcasecompare("default", tok)) { + state = HOSTVALID; + retcode = NETRC_SUCCESS; /* we did find our host */ + } + break; + case MACDEF: + if(!strlen(tok)) { + state = NOTHING; + } + break; + case HOSTFOUND: + if(strcasecompare(host, tok)) { + /* and yes, this is our host! */ + state = HOSTVALID; + retcode = NETRC_SUCCESS; /* we did find our host */ + } + else + /* not our host */ + state = NOTHING; + break; + case HOSTVALID: + /* we are now parsing sub-keywords concerning "our" host */ + if(state_login) { + if(specific_login) { + state_our_login = !Curl_timestrcmp(login, tok); + } + else if(!login || Curl_timestrcmp(login, tok)) { + if(login_alloc) { + free(login); + login_alloc = FALSE; + } + login = strdup(tok); + if(!login) { + retcode = NETRC_FAILED; /* allocation failed */ + goto out; + } + login_alloc = TRUE; + } + state_login = 0; + } + else if(state_password) { + if((state_our_login || !specific_login) + && (!password || Curl_timestrcmp(password, tok))) { + if(password_alloc) { + free(password); + password_alloc = FALSE; + } + password = strdup(tok); + if(!password) { + retcode = NETRC_FAILED; /* allocation failed */ + goto out; + } + password_alloc = TRUE; + } + state_password = 0; + } + else if(strcasecompare("login", tok)) + state_login = 1; + else if(strcasecompare("password", tok)) + state_password = 1; + else if(strcasecompare("machine", tok)) { + /* ok, there's machine here go => */ + state = HOSTFOUND; + state_our_login = FALSE; + } + break; + } /* switch (state) */ + tok = ++tok_end; + } + } /* while Curl_get_line() */ + +out: + if(!retcode) { + /* success */ + if(login_alloc) { + if(*loginp) + free(*loginp); + *loginp = login; + } + if(password_alloc) { + if(*passwordp) + free(*passwordp); + *passwordp = password; + } + } + else { + if(login_alloc) + free(login); + if(password_alloc) + free(password); + } + fclose(file); + } + + return retcode; +} + +/* + * @unittest: 1304 + * + * *loginp and *passwordp MUST be allocated if they aren't NULL when passed + * in. + */ +int Curl_parsenetrc(const char *host, char **loginp, char **passwordp, + char *netrcfile) +{ + int retcode = 1; + char *filealloc = NULL; + + if(!netrcfile) { +#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) + char pwbuf[1024]; +#endif + char *home = NULL; + char *homea = curl_getenv("HOME"); /* portable environment reader */ + if(homea) { + home = homea; +#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) + } + else { + struct passwd pw, *pw_res; + if(!getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) + && pw_res) { + home = pw.pw_dir; + } +#elif defined(HAVE_GETPWUID) && defined(HAVE_GETEUID) + } + else { + struct passwd *pw; + pw = getpwuid(geteuid()); + if(pw) { + home = pw->pw_dir; + } +#elif defined(_WIN32) + } + else { + homea = curl_getenv("USERPROFILE"); + if(homea) { + home = homea; + } +#endif + } + + if(!home) + return retcode; /* no home directory found (or possibly out of + memory) */ + + filealloc = curl_maprintf("%s%s.netrc", home, DIR_CHAR); + if(!filealloc) { + free(homea); + return -1; + } + retcode = parsenetrc(host, loginp, passwordp, filealloc); + free(filealloc); +#ifdef _WIN32 + if(retcode == NETRC_FILE_MISSING) { + /* fallback to the old-style "_netrc" file */ + filealloc = curl_maprintf("%s%s_netrc", home, DIR_CHAR); + if(!filealloc) { + free(homea); + return -1; + } + retcode = parsenetrc(host, loginp, passwordp, filealloc); + free(filealloc); + } +#endif + free(homea); + } + else + retcode = parsenetrc(host, loginp, passwordp, netrcfile); + return retcode; +} + +#endif diff --git a/Utilities/cmcurl/lib/netrc.h b/Utilities/cmcurl/lib/netrc.h new file mode 100644 index 0000000..9f2815f --- /dev/null +++ b/Utilities/cmcurl/lib/netrc.h @@ -0,0 +1,43 @@ +#ifndef HEADER_CURL_NETRC_H +#define HEADER_CURL_NETRC_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#ifndef CURL_DISABLE_NETRC + +/* returns -1 on failure, 0 if the host is found, 1 is the host isn't found */ +int Curl_parsenetrc(const char *host, char **loginp, + char **passwordp, char *filename); + /* Assume: (*passwordp)[0]=0, host[0] != 0. + * If (*loginp)[0] = 0, search for login and password within a machine + * section in the netrc. + * If (*loginp)[0] != 0, search for password within machine and login. + */ +#else +/* disabled */ +#define Curl_parsenetrc(a,b,c,d,e,f) 1 +#endif + +#endif /* HEADER_CURL_NETRC_H */ diff --git a/Utilities/cmcurl/lib/nonblock.c b/Utilities/cmcurl/lib/nonblock.c new file mode 100644 index 0000000..f4eb656 --- /dev/null +++ b/Utilities/cmcurl/lib/nonblock.c @@ -0,0 +1,84 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include "nonblock.h" + +/* + * curlx_nonblock() set the given socket to either blocking or non-blocking + * mode based on the 'nonblock' boolean argument. This function is highly + * portable. + */ +int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ + int nonblock /* TRUE or FALSE */) +{ +#if defined(HAVE_FCNTL_O_NONBLOCK) + /* most recent unix versions */ + int flags; + flags = sfcntl(sockfd, F_GETFL, 0); + if(nonblock) + return sfcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + return sfcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); + +#elif defined(HAVE_IOCTL_FIONBIO) + + /* older unix versions */ + int flags = nonblock ? 1 : 0; + return ioctl(sockfd, FIONBIO, &flags); + +#elif defined(HAVE_IOCTLSOCKET_FIONBIO) + + /* Windows */ + unsigned long flags = nonblock ? 1UL : 0UL; + return ioctlsocket(sockfd, FIONBIO, &flags); + +#elif defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO) + + /* Amiga */ + long flags = nonblock ? 1L : 0L; + return IoctlSocket(sockfd, FIONBIO, (char *)&flags); + +#elif defined(HAVE_SETSOCKOPT_SO_NONBLOCK) + + /* Orbis OS */ + long b = nonblock ? 1L : 0L; + return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b)); + +#else +# error "no non-blocking method was found/used/set" +#endif +} diff --git a/Utilities/cmcurl/lib/nonblock.h b/Utilities/cmcurl/lib/nonblock.h new file mode 100644 index 0000000..4a1a615 --- /dev/null +++ b/Utilities/cmcurl/lib/nonblock.h @@ -0,0 +1,32 @@ +#ifndef HEADER_CURL_NONBLOCK_H +#define HEADER_CURL_NONBLOCK_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include <curl/curl.h> /* for curl_socket_t */ + +int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ + int nonblock /* TRUE or FALSE */); + +#endif /* HEADER_CURL_NONBLOCK_H */ diff --git a/Utilities/cmcurl/lib/noproxy.c b/Utilities/cmcurl/lib/noproxy.c new file mode 100644 index 0000000..2b9908d --- /dev/null +++ b/Utilities/cmcurl/lib/noproxy.c @@ -0,0 +1,266 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_PROXY + +#include "inet_pton.h" +#include "strcase.h" +#include "noproxy.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +/* + * Curl_cidr4_match() returns TRUE if the given IPv4 address is within the + * specified CIDR address range. + */ +UNITTEST bool Curl_cidr4_match(const char *ipv4, /* 1.2.3.4 address */ + const char *network, /* 1.2.3.4 address */ + unsigned int bits) +{ + unsigned int address = 0; + unsigned int check = 0; + + if(bits > 32) + /* strange input */ + return FALSE; + + if(1 != Curl_inet_pton(AF_INET, ipv4, &address)) + return FALSE; + if(1 != Curl_inet_pton(AF_INET, network, &check)) + return FALSE; + + if(bits && (bits != 32)) { + unsigned int mask = 0xffffffff << (32 - bits); + unsigned int haddr = htonl(address); + unsigned int hcheck = htonl(check); +#if 0 + fprintf(stderr, "Host %s (%x) network %s (%x) bits %u mask %x => %x\n", + ipv4, haddr, network, hcheck, bits, mask, + (haddr ^ hcheck) & mask); +#endif + if((haddr ^ hcheck) & mask) + return FALSE; + return TRUE; + } + return (address == check); +} + +UNITTEST bool Curl_cidr6_match(const char *ipv6, + const char *network, + unsigned int bits) +{ +#ifdef ENABLE_IPV6 + int bytes; + int rest; + unsigned char address[16]; + unsigned char check[16]; + + if(!bits) + bits = 128; + + bytes = bits/8; + rest = bits & 0x07; + if(1 != Curl_inet_pton(AF_INET6, ipv6, address)) + return FALSE; + if(1 != Curl_inet_pton(AF_INET6, network, check)) + return FALSE; + if((bytes > 16) || ((bytes == 16) && rest)) + return FALSE; + if(bytes && memcmp(address, check, bytes)) + return FALSE; + if(rest && !((address[bytes] ^ check[bytes]) & (0xff << (8 - rest)))) + return FALSE; + + return TRUE; +#else + (void)ipv6; + (void)network; + (void)bits; + return FALSE; +#endif +} + +enum nametype { + TYPE_HOST, + TYPE_IPV4, + TYPE_IPV6 +}; + +/**************************************************************** +* Checks if the host is in the noproxy list. returns TRUE if it matches and +* therefore the proxy should NOT be used. +****************************************************************/ +bool Curl_check_noproxy(const char *name, const char *no_proxy, + bool *spacesep) +{ + char hostip[128]; + *spacesep = FALSE; + /* + * If we don't have a hostname at all, like for example with a FILE + * transfer, we have nothing to interrogate the noproxy list with. + */ + if(!name || name[0] == '\0') + return FALSE; + + /* no_proxy=domain1.dom,host.domain2.dom + * (a comma-separated list of hosts which should + * not be proxied, or an asterisk to override + * all proxy variables) + */ + if(no_proxy && no_proxy[0]) { + const char *p = no_proxy; + size_t namelen; + enum nametype type = TYPE_HOST; + if(!strcmp("*", no_proxy)) + return TRUE; + + /* NO_PROXY was specified and it wasn't just an asterisk */ + + if(name[0] == '[') { + char *endptr; + /* IPv6 numerical address */ + endptr = strchr(name, ']'); + if(!endptr) + return FALSE; + name++; + namelen = endptr - name; + if(namelen >= sizeof(hostip)) + return FALSE; + memcpy(hostip, name, namelen); + hostip[namelen] = 0; + name = hostip; + type = TYPE_IPV6; + } + else { + unsigned int address; + namelen = strlen(name); + if(1 == Curl_inet_pton(AF_INET, name, &address)) + type = TYPE_IPV4; + else { + /* ignore trailing dots in the host name */ + if(name[namelen - 1] == '.') + namelen--; + } + } + + while(*p) { + const char *token; + size_t tokenlen = 0; + bool match = FALSE; + + /* pass blanks */ + while(*p && ISBLANK(*p)) + p++; + + token = p; + /* pass over the pattern */ + while(*p && !ISBLANK(*p) && (*p != ',')) { + p++; + tokenlen++; + } + + if(tokenlen) { + switch(type) { + case TYPE_HOST: + /* ignore trailing dots in the token to check */ + if(token[tokenlen - 1] == '.') + tokenlen--; + + if(tokenlen && (*token == '.')) { + /* ignore leading token dot as well */ + token++; + 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 = strncasecompare(token, name, namelen); + else if(tokenlen < namelen) { + /* case B, tailmatch domain */ + match = (name[namelen - tokenlen - 1] == '.') && + strncasecompare(token, name + (namelen - tokenlen), + tokenlen); + } + /* case C passes through, not a match */ + break; + case TYPE_IPV4: + /* FALLTHROUGH */ + case TYPE_IPV6: { + const char *check = token; + char *slash; + unsigned int bits = 0; + char checkip[128]; + if(tokenlen >= sizeof(checkip)) + /* this cannot match */ + break; + /* copy the check name to a temp buffer */ + memcpy(checkip, check, tokenlen); + checkip[tokenlen] = 0; + check = checkip; + + slash = strchr(check, '/'); + /* if the slash is part of this token, use it */ + if(slash) { + bits = atoi(slash + 1); + *slash = 0; /* null terminate there */ + } + if(type == TYPE_IPV6) + match = Curl_cidr6_match(name, check, bits); + else + match = Curl_cidr4_match(name, check, bits); + break; + } + } + if(match) + return TRUE; + } /* if(tokenlen) */ + /* pass blanks after pattern */ + while(ISBLANK(*p)) + p++; + /* if not a comma! */ + if(*p && (*p != ',')) { + *spacesep = TRUE; + continue; + } + /* pass any number of commas */ + while(*p == ',') + p++; + } /* while(*p) */ + } /* NO_PROXY was specified and it wasn't just an asterisk */ + + return FALSE; +} + +#endif /* CURL_DISABLE_PROXY */ diff --git a/Utilities/cmcurl/lib/noproxy.h b/Utilities/cmcurl/lib/noproxy.h new file mode 100644 index 0000000..a3a6807 --- /dev/null +++ b/Utilities/cmcurl/lib/noproxy.h @@ -0,0 +1,45 @@ +#ifndef HEADER_CURL_NOPROXY_H +#define HEADER_CURL_NOPROXY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifndef CURL_DISABLE_PROXY + +#ifdef DEBUGBUILD + +UNITTEST bool Curl_cidr4_match(const char *ipv4, /* 1.2.3.4 address */ + const char *network, /* 1.2.3.4 address */ + unsigned int bits); +UNITTEST bool Curl_cidr6_match(const char *ipv6, + const char *network, + unsigned int bits); +#endif + +bool Curl_check_noproxy(const char *name, const char *no_proxy, + bool *spacesep); + +#endif + +#endif /* HEADER_CURL_NOPROXY_H */ diff --git a/Utilities/cmcurl/lib/openldap.c b/Utilities/cmcurl/lib/openldap.c new file mode 100644 index 0000000..131f474 --- /dev/null +++ b/Utilities/cmcurl/lib/openldap.c @@ -0,0 +1,1212 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Howard Chu, <hyc@openldap.org> + * + * 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_LDAP) && defined(USE_OPENLDAP) + +/* + * Notice that USE_OPENLDAP is only a source code selection switch. When + * libcurl is built with USE_OPENLDAP defined the libcurl source code that + * gets compiled is the code from openldap.c, otherwise the code that gets + * compiled is the code from ldap.c. + * + * When USE_OPENLDAP is defined a recent version of the OpenLDAP library + * might be required for compilation and runtime. In order to use ancient + * OpenLDAP library versions, USE_OPENLDAP shall not be defined. + */ + +#include <ldap.h> + +#include "urldata.h" +#include <curl/curl.h> +#include "sendf.h" +#include "vtls/vtls.h" +#include "transfer.h" +#include "curl_ldap.h" +#include "curl_base64.h" +#include "cfilters.h" +#include "connect.h" +#include "curl_sasl.h" +#include "strcase.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Uncommenting this will enable the built-in debug logging of the openldap + * library. The debug log level can be set using the CURL_OPENLDAP_TRACE + * environment variable. The debug output is written to stderr. + * + * The library supports the following debug flags: + * LDAP_DEBUG_NONE 0x0000 + * LDAP_DEBUG_TRACE 0x0001 + * LDAP_DEBUG_CONSTRUCT 0x0002 + * LDAP_DEBUG_DESTROY 0x0004 + * LDAP_DEBUG_PARAMETER 0x0008 + * LDAP_DEBUG_ANY 0xffff + * + * For example, use CURL_OPENLDAP_TRACE=0 for no debug, + * CURL_OPENLDAP_TRACE=2 for LDAP_DEBUG_CONSTRUCT messages only, + * CURL_OPENLDAP_TRACE=65535 for all debug message levels. + */ +/* #define CURL_OPENLDAP_DEBUG */ + +/* Machine states. */ +typedef enum { + OLDAP_STOP, /* Do nothing state, stops the state machine */ + OLDAP_SSL, /* Performing SSL handshake. */ + OLDAP_STARTTLS, /* STARTTLS request sent. */ + OLDAP_TLS, /* Performing TLS handshake. */ + OLDAP_MECHS, /* Get SASL authentication mechanisms. */ + OLDAP_SASL, /* SASL binding reply. */ + OLDAP_BIND, /* Simple bind reply. */ + OLDAP_BINDV2, /* Simple bind reply in protocol version 2. */ + OLDAP_LAST /* Never used */ +} ldapstate; + +#ifndef _LDAP_PVT_H +extern int ldap_pvt_url_scheme2proto(const char *); +extern int ldap_init_fd(ber_socket_t fd, int proto, const char *url, + LDAP **ld); +#endif + +static CURLcode oldap_setup_connection(struct Curl_easy *data, + struct connectdata *conn); +static CURLcode oldap_do(struct Curl_easy *data, bool *done); +static CURLcode oldap_done(struct Curl_easy *data, CURLcode, bool); +static CURLcode oldap_connect(struct Curl_easy *data, bool *done); +static CURLcode oldap_connecting(struct Curl_easy *data, bool *done); +static CURLcode oldap_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead); + +static CURLcode oldap_perform_auth(struct Curl_easy *data, const char *mech, + const struct bufref *initresp); +static CURLcode oldap_continue_auth(struct Curl_easy *data, const char *mech, + const struct bufref *resp); +static CURLcode oldap_cancel_auth(struct Curl_easy *data, const char *mech); +static CURLcode oldap_get_message(struct Curl_easy *data, struct bufref *out); + +static Curl_recv oldap_recv; + +/* + * LDAP protocol handler. + */ + +const struct Curl_handler Curl_handler_ldap = { + "LDAP", /* scheme */ + oldap_setup_connection, /* setup_connection */ + oldap_do, /* do_it */ + oldap_done, /* done */ + ZERO_NULL, /* do_more */ + oldap_connect, /* connect_it */ + oldap_connecting, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + oldap_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_LDAP, /* defport */ + CURLPROTO_LDAP, /* protocol */ + CURLPROTO_LDAP, /* family */ + PROTOPT_NONE /* flags */ +}; + +#ifdef USE_SSL +/* + * LDAPS protocol handler. + */ + +const struct Curl_handler Curl_handler_ldaps = { + "LDAPS", /* scheme */ + oldap_setup_connection, /* setup_connection */ + oldap_do, /* do_it */ + oldap_done, /* done */ + ZERO_NULL, /* do_more */ + oldap_connect, /* connect_it */ + oldap_connecting, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + oldap_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_LDAPS, /* defport */ + CURLPROTO_LDAPS, /* protocol */ + CURLPROTO_LDAP, /* family */ + PROTOPT_SSL /* flags */ +}; +#endif + +/* SASL parameters for the ldap protocol */ +static const struct SASLproto saslldap = { + "ldap", /* The service name */ + oldap_perform_auth, /* Send authentication command */ + oldap_continue_auth, /* Send authentication continuation */ + oldap_cancel_auth, /* Send authentication cancellation */ + oldap_get_message, /* Get SASL response message */ + 0, /* Maximum initial response length (no max) */ + LDAP_SASL_BIND_IN_PROGRESS, /* Code received when continuation is expected */ + LDAP_SUCCESS, /* Code to receive upon authentication success */ + SASL_AUTH_NONE, /* Default mechanisms */ + 0 /* Configuration flags */ +}; + +struct ldapconninfo { + struct SASL sasl; /* SASL-related parameters */ + LDAP *ld; /* Openldap connection handle. */ + Curl_recv *recv; /* For stacking SSL handler */ + Curl_send *send; + struct berval *servercred; /* SASL data from server. */ + ldapstate state; /* Current machine state. */ + int proto; /* LDAP_PROTO_TCP/LDAP_PROTO_UDP/LDAP_PROTO_IPC */ + int msgid; /* Current message id. */ +}; + +struct ldapreqinfo { + int msgid; + int nument; +}; + +/* + * oldap_state() + * + * This is the ONLY way to change LDAP state! + */ +static void oldap_state(struct Curl_easy *data, ldapstate newstate) +{ + struct ldapconninfo *ldapc = data->conn->proto.ldapc; + +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[] = { + "STOP", + "SSL", + "STARTTLS", + "TLS", + "MECHS", + "SASL", + "BIND", + "BINDV2", + /* LAST */ + }; + + if(ldapc->state != newstate) + infof(data, "LDAP %p state change from %s to %s", + (void *)ldapc, names[ldapc->state], names[newstate]); +#endif + + ldapc->state = newstate; +} + +/* Map some particular LDAP error codes to CURLcode values. */ +static CURLcode oldap_map_error(int rc, CURLcode result) +{ + switch(rc) { + case LDAP_NO_MEMORY: + result = CURLE_OUT_OF_MEMORY; + break; + case LDAP_INVALID_CREDENTIALS: + result = CURLE_LOGIN_DENIED; + break; + case LDAP_PROTOCOL_ERROR: + result = CURLE_UNSUPPORTED_PROTOCOL; + break; + case LDAP_INSUFFICIENT_ACCESS: + result = CURLE_REMOTE_ACCESS_DENIED; + break; + } + return result; +} + +static CURLcode oldap_url_parse(struct Curl_easy *data, LDAPURLDesc **ludp) +{ + CURLcode result = CURLE_OK; + int rc = LDAP_URL_ERR_BADURL; + static const char * const url_errs[] = { + "success", + "out of memory", + "bad parameter", + "unrecognized scheme", + "unbalanced delimiter", + "bad URL", + "bad host or port", + "bad or missing attributes", + "bad or missing scope", + "bad or missing filter", + "bad or missing extensions" + }; + + *ludp = NULL; + if(!data->state.up.user && !data->state.up.password && + !data->state.up.options) + rc = ldap_url_parse(data->state.url, ludp); + if(rc != LDAP_URL_SUCCESS) { + const char *msg = "url parsing problem"; + + result = rc == LDAP_URL_ERR_MEM? CURLE_OUT_OF_MEMORY: CURLE_URL_MALFORMAT; + rc -= LDAP_URL_SUCCESS; + if((size_t) rc < sizeof(url_errs) / sizeof(url_errs[0])) + msg = url_errs[rc]; + failf(data, "LDAP local: %s", msg); + } + return result; +} + +/* Parse the login options. */ +static CURLcode oldap_parse_login_options(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct ldapconninfo *li = conn->proto.ldapc; + const char *ptr = conn->options; + + while(!result && ptr && *ptr) { + const char *key = ptr; + const char *value; + + while(*ptr && *ptr != '=') + ptr++; + + value = ptr + 1; + + while(*ptr && *ptr != ';') + ptr++; + + if(checkprefix("AUTH=", key)) + result = Curl_sasl_parse_url_auth_option(&li->sasl, value, ptr - value); + else + result = CURLE_SETOPT_OPTION_SYNTAX; + + if(*ptr == ';') + ptr++; + } + + return result == CURLE_URL_MALFORMAT? CURLE_SETOPT_OPTION_SYNTAX: result; +} + +static CURLcode oldap_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result; + LDAPURLDesc *lud; + (void)conn; + + /* Early URL syntax check. */ + result = oldap_url_parse(data, &lud); + ldap_free_urldesc(lud); + + return result; +} + +/* + * Get the SASL authentication challenge from the server credential buffer. + */ +static CURLcode oldap_get_message(struct Curl_easy *data, struct bufref *out) +{ + struct berval *servercred = data->conn->proto.ldapc->servercred; + + if(!servercred || !servercred->bv_val) + return CURLE_WEIRD_SERVER_REPLY; + Curl_bufref_set(out, servercred->bv_val, servercred->bv_len, NULL); + return CURLE_OK; +} + +/* + * Sends an initial SASL bind request to the server. + */ +static CURLcode oldap_perform_auth(struct Curl_easy *data, const char *mech, + const struct bufref *initresp) +{ + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + CURLcode result = CURLE_OK; + struct berval cred; + struct berval *pcred = &cred; + int rc; + + cred.bv_val = (char *) Curl_bufref_ptr(initresp); + cred.bv_len = Curl_bufref_len(initresp); + if(!cred.bv_val) + pcred = NULL; + rc = ldap_sasl_bind(li->ld, NULL, mech, pcred, NULL, NULL, &li->msgid); + if(rc != LDAP_SUCCESS) + result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return result; +} + +/* + * Sends SASL continuation. + */ +static CURLcode oldap_continue_auth(struct Curl_easy *data, const char *mech, + const struct bufref *resp) +{ + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + CURLcode result = CURLE_OK; + struct berval cred; + struct berval *pcred = &cred; + int rc; + + cred.bv_val = (char *) Curl_bufref_ptr(resp); + cred.bv_len = Curl_bufref_len(resp); + if(!cred.bv_val) + pcred = NULL; + rc = ldap_sasl_bind(li->ld, NULL, mech, pcred, NULL, NULL, &li->msgid); + if(rc != LDAP_SUCCESS) + result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return result; +} + +/* + * Sends SASL bind cancellation. + */ +static CURLcode oldap_cancel_auth(struct Curl_easy *data, const char *mech) +{ + struct ldapconninfo *li = data->conn->proto.ldapc; + CURLcode result = CURLE_OK; + int rc = ldap_sasl_bind(li->ld, NULL, LDAP_SASL_NULL, NULL, NULL, NULL, + &li->msgid); + + (void)mech; + if(rc != LDAP_SUCCESS) + result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return result; +} + +/* Starts LDAP simple bind. */ +static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + char *binddn = NULL; + struct berval passwd; + int rc; + + passwd.bv_val = NULL; + passwd.bv_len = 0; + + if(data->state.aptr.user) { + binddn = conn->user; + passwd.bv_val = conn->passwd; + passwd.bv_len = strlen(passwd.bv_val); + } + + rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd, + NULL, NULL, &li->msgid); + if(rc == LDAP_SUCCESS) + oldap_state(data, newstate); + else + result = oldap_map_error(rc, + data->state.aptr.user? + CURLE_LOGIN_DENIED: CURLE_LDAP_CANNOT_BIND); + return result; +} + +/* Query the supported SASL authentication mechanisms. */ +static CURLcode oldap_perform_mechs(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct ldapconninfo *li = data->conn->proto.ldapc; + int rc; + static const char * const supportedSASLMechanisms[] = { + "supportedSASLMechanisms", + NULL + }; + + rc = ldap_search_ext(li->ld, "", LDAP_SCOPE_BASE, "(objectclass=*)", + (char **) supportedSASLMechanisms, 0, + NULL, NULL, NULL, 0, &li->msgid); + if(rc == LDAP_SUCCESS) + oldap_state(data, OLDAP_MECHS); + else + result = oldap_map_error(rc, CURLE_LOGIN_DENIED); + return result; +} + +/* Starts SASL bind. */ +static CURLcode oldap_perform_sasl(struct Curl_easy *data) +{ + saslprogress progress = SASL_IDLE; + struct ldapconninfo *li = data->conn->proto.ldapc; + CURLcode result = Curl_sasl_start(&li->sasl, data, TRUE, &progress); + + oldap_state(data, OLDAP_SASL); + if(!result && progress != SASL_INPROGRESS) + result = CURLE_LOGIN_DENIED; + return result; +} + +#ifdef USE_SSL +static Sockbuf_IO ldapsb_tls; + +static bool ssl_installed(struct connectdata *conn) +{ + return conn->proto.ldapc->recv != NULL; +} + +static CURLcode oldap_ssl_connect(struct Curl_easy *data, ldapstate newstate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + bool ssldone = 0; + + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + if(!result) { + oldap_state(data, newstate); + + if(ssldone) { + Sockbuf *sb; + + /* Install the libcurl SSL handlers into the sockbuf. */ + ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb); + ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, data); + li->recv = conn->recv[FIRSTSOCKET]; + li->send = conn->send[FIRSTSOCKET]; + } + } + + return result; +} + +/* Send the STARTTLS request */ +static CURLcode oldap_perform_starttls(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct ldapconninfo *li = data->conn->proto.ldapc; + int rc = ldap_start_tls(li->ld, NULL, NULL, &li->msgid); + + if(rc == LDAP_SUCCESS) + oldap_state(data, OLDAP_STARTTLS); + else + result = oldap_map_error(rc, CURLE_USE_SSL_FAILED); + return result; +} +#endif + +static CURLcode oldap_connect(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + struct ldapconninfo *li; + static const int version = LDAP_VERSION3; + int rc; + char *hosturl; +#ifdef CURL_OPENLDAP_DEBUG + static int do_trace = -1; +#endif + + (void)done; + + DEBUGASSERT(!conn->proto.ldapc); + li = calloc(1, sizeof(struct ldapconninfo)); + if(!li) + return CURLE_OUT_OF_MEMORY; + else { + CURLcode result; + li->proto = ldap_pvt_url_scheme2proto(data->state.up.scheme); + conn->proto.ldapc = li; + + /* Initialize the SASL storage */ + Curl_sasl_init(&li->sasl, data, &saslldap); + + /* Clear the TLS upgraded flag */ + conn->bits.tls_upgraded = FALSE; + + result = oldap_parse_login_options(conn); + if(result) + return result; + } + + hosturl = aprintf("ldap%s://%s:%d", + conn->handler->flags & PROTOPT_SSL? "s": "", + conn->host.name, conn->remote_port); + if(!hosturl) + return CURLE_OUT_OF_MEMORY; + + rc = ldap_init_fd(conn->sock[FIRSTSOCKET], li->proto, hosturl, &li->ld); + if(rc) { + failf(data, "LDAP local: Cannot connect to %s, %s", + hosturl, ldap_err2string(rc)); + free(hosturl); + return CURLE_COULDNT_CONNECT; + } + + free(hosturl); + +#ifdef CURL_OPENLDAP_DEBUG + if(do_trace < 0) { + const char *env = getenv("CURL_OPENLDAP_TRACE"); + do_trace = (env && strtol(env, NULL, 10) > 0); + } + if(do_trace) + ldap_set_option(li->ld, LDAP_OPT_DEBUG_LEVEL, &do_trace); +#endif + + /* Try version 3 first. */ + ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &version); + + /* Do not chase referrals. */ + ldap_set_option(li->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); + +#ifdef USE_SSL + if(conn->handler->flags & PROTOPT_SSL) + return oldap_ssl_connect(data, OLDAP_SSL); + + if(data->set.use_ssl) { + CURLcode result = oldap_perform_starttls(data); + + if(!result || data->set.use_ssl != CURLUSESSL_TRY) + return result; + } +#endif + + if(li->sasl.prefmech != SASL_AUTH_NONE) + return oldap_perform_mechs(data); + + /* Force bind even if anonymous bind is not needed in protocol version 3 + to detect missing version 3 support. */ + return oldap_perform_bind(data, OLDAP_BIND); +} + +/* Handle the supported SASL mechanisms query response */ +static CURLcode oldap_state_mechs_resp(struct Curl_easy *data, + LDAPMessage *msg, int code) +{ + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + int rc; + BerElement *ber = NULL; + CURLcode result = CURLE_OK; + struct berval bv, *bvals; + + switch(ldap_msgtype(msg)) { + case LDAP_RES_SEARCH_ENTRY: + /* Got a list of supported SASL mechanisms. */ + if(code != LDAP_SUCCESS && code != LDAP_NO_RESULTS_RETURNED) + return CURLE_LOGIN_DENIED; + + rc = ldap_get_dn_ber(li->ld, msg, &ber, &bv); + if(rc < 0) + return oldap_map_error(rc, CURLE_BAD_CONTENT_ENCODING); + for(rc = ldap_get_attribute_ber(li->ld, msg, ber, &bv, &bvals); + rc == LDAP_SUCCESS; + rc = ldap_get_attribute_ber(li->ld, msg, ber, &bv, &bvals)) { + int i; + + if(!bv.bv_val) + break; + + if(bvals) { + for(i = 0; bvals[i].bv_val; i++) { + size_t llen; + unsigned short mech = Curl_sasl_decode_mech((char *) bvals[i].bv_val, + bvals[i].bv_len, &llen); + if(bvals[i].bv_len == llen) + li->sasl.authmechs |= mech; + } + ber_memfree(bvals); + } + } + ber_free(ber, 0); + break; + + case LDAP_RES_SEARCH_RESULT: + switch(code) { + case LDAP_SIZELIMIT_EXCEEDED: + infof(data, "Too many authentication mechanisms\n"); + /* FALLTHROUGH */ + case LDAP_SUCCESS: + case LDAP_NO_RESULTS_RETURNED: + if(Curl_sasl_can_authenticate(&li->sasl, data)) + result = oldap_perform_sasl(data); + else + result = CURLE_LOGIN_DENIED; + break; + default: + result = oldap_map_error(code, CURLE_LOGIN_DENIED); + break; + } + break; + default: + break; + } + return result; +} + +/* Handle a SASL bind response. */ +static CURLcode oldap_state_sasl_resp(struct Curl_easy *data, + LDAPMessage *msg, int code) +{ + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + CURLcode result = CURLE_OK; + saslprogress progress; + int rc; + + li->servercred = NULL; + rc = ldap_parse_sasl_bind_result(li->ld, msg, &li->servercred, 0); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: sasl ldap_parse_result %s", ldap_err2string(rc)); + result = oldap_map_error(rc, CURLE_LOGIN_DENIED); + } + else { + result = Curl_sasl_continue(&li->sasl, data, code, &progress); + if(!result && progress != SASL_INPROGRESS) + oldap_state(data, OLDAP_STOP); + } + + if(li->servercred) + ber_bvfree(li->servercred); + return result; +} + +/* Handle a simple bind response. */ +static CURLcode oldap_state_bind_resp(struct Curl_easy *data, LDAPMessage *msg, + int code) +{ + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + CURLcode result = CURLE_OK; + struct berval *bv = NULL; + int rc; + + if(code != LDAP_SUCCESS) + return oldap_map_error(code, CURLE_LDAP_CANNOT_BIND); + + rc = ldap_parse_sasl_bind_result(li->ld, msg, &bv, 0); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: bind ldap_parse_sasl_bind_result %s", + ldap_err2string(rc)); + result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + } + else + oldap_state(data, OLDAP_STOP); + + if(bv) + ber_bvfree(bv); + return result; +} + +static CURLcode oldap_connecting(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + LDAPMessage *msg = NULL; + struct timeval tv = {0, 0}; + int code = LDAP_SUCCESS; + int rc; + + if(li->state != OLDAP_SSL && li->state != OLDAP_TLS) { + /* Get response to last command. */ + rc = ldap_result(li->ld, li->msgid, LDAP_MSG_ONE, &tv, &msg); + switch(rc) { + case 0: /* Timed out. */ + return CURLE_OK; + case LDAP_RES_SEARCH_ENTRY: + case LDAP_RES_SEARCH_REFERENCE: + break; + default: + li->msgid = 0; /* Nothing to abandon upon error. */ + if(rc < 0) { + failf(data, "LDAP local: connecting ldap_result %s", + ldap_err2string(rc)); + return oldap_map_error(rc, CURLE_COULDNT_CONNECT); + } + break; + } + + /* Get error code from message. */ + rc = ldap_parse_result(li->ld, msg, &code, NULL, NULL, NULL, NULL, 0); + if(rc) + code = rc; + else { + /* store the latest code for later retrieval */ + data->info.httpcode = code; + } + + /* If protocol version 3 is not supported, fallback to version 2. */ + if(code == LDAP_PROTOCOL_ERROR && li->state != OLDAP_BINDV2 && +#ifdef USE_SSL + (ssl_installed(conn) || data->set.use_ssl <= CURLUSESSL_TRY) && +#endif + li->sasl.prefmech == SASL_AUTH_NONE) { + static const int version = LDAP_VERSION2; + + ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &version); + ldap_msgfree(msg); + return oldap_perform_bind(data, OLDAP_BINDV2); + } + } + + /* Handle response message according to current state. */ + switch(li->state) { + +#ifdef USE_SSL + case OLDAP_SSL: + result = oldap_ssl_connect(data, OLDAP_SSL); + if(!result && ssl_installed(conn)) { + if(li->sasl.prefmech != SASL_AUTH_NONE) + result = oldap_perform_mechs(data); + else + result = oldap_perform_bind(data, OLDAP_BIND); + } + break; + case OLDAP_STARTTLS: + if(code != LDAP_SUCCESS) { + if(data->set.use_ssl != CURLUSESSL_TRY) + result = oldap_map_error(code, CURLE_USE_SSL_FAILED); + else if(li->sasl.prefmech != SASL_AUTH_NONE) + result = oldap_perform_mechs(data); + else + result = oldap_perform_bind(data, OLDAP_BIND); + break; + } + /* FALLTHROUGH */ + case OLDAP_TLS: + result = oldap_ssl_connect(data, OLDAP_TLS); + if(result && data->set.use_ssl != CURLUSESSL_TRY) + result = oldap_map_error(code, CURLE_USE_SSL_FAILED); + else if(ssl_installed(conn)) { + conn->bits.tls_upgraded = TRUE; + if(li->sasl.prefmech != SASL_AUTH_NONE) + result = oldap_perform_mechs(data); + else if(data->state.aptr.user) + result = oldap_perform_bind(data, OLDAP_BIND); + else { + /* Version 3 supported: no bind required */ + oldap_state(data, OLDAP_STOP); + result = CURLE_OK; + } + } + break; +#endif + + case OLDAP_MECHS: + result = oldap_state_mechs_resp(data, msg, code); + break; + case OLDAP_SASL: + result = oldap_state_sasl_resp(data, msg, code); + break; + case OLDAP_BIND: + case OLDAP_BINDV2: + result = oldap_state_bind_resp(data, msg, code); + break; + default: + /* internal error */ + result = CURLE_COULDNT_CONNECT; + break; + } + + ldap_msgfree(msg); + + *done = li->state == OLDAP_STOP; + if(*done) + conn->recv[FIRSTSOCKET] = oldap_recv; + + if(result && li->msgid) { + ldap_abandon_ext(li->ld, li->msgid, NULL, NULL); + li->msgid = 0; + } + return result; +} + +static CURLcode oldap_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + struct ldapconninfo *li = conn->proto.ldapc; + (void) dead_connection; +#ifndef USE_SSL + (void)data; +#endif + + if(li) { + if(li->ld) { +#ifdef USE_SSL + if(ssl_installed(conn)) { + Sockbuf *sb; + ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb); + ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, data); + } +#endif + ldap_unbind_ext(li->ld, NULL, NULL); + li->ld = NULL; + } + Curl_sasl_cleanup(conn, li->sasl.authused); + conn->proto.ldapc = NULL; + free(li); + } + return CURLE_OK; +} + +static CURLcode oldap_do(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + struct ldapreqinfo *lr; + CURLcode result; + int rc; + LDAPURLDesc *lud; + int msgid; + + connkeep(conn, "OpenLDAP do"); + + infof(data, "LDAP local: %s", data->state.url); + + result = oldap_url_parse(data, &lud); + if(!result) { + Sockbuf *sb; + /* re-install the libcurl SSL handlers into the sockbuf. */ + ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb); + ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, data); + + rc = ldap_search_ext(li->ld, lud->lud_dn, lud->lud_scope, + lud->lud_filter, lud->lud_attrs, 0, + NULL, NULL, NULL, 0, &msgid); + ldap_free_urldesc(lud); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ldap_search_ext %s", ldap_err2string(rc)); + result = CURLE_LDAP_SEARCH_FAILED; + } + else { + lr = calloc(1, sizeof(struct ldapreqinfo)); + if(!lr) { + ldap_abandon_ext(li->ld, msgid, NULL, NULL); + result = CURLE_OUT_OF_MEMORY; + } + else { + lr->msgid = msgid; + data->req.p.ldap = lr; + Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + *done = TRUE; + } + } + } + return result; +} + +static CURLcode oldap_done(struct Curl_easy *data, CURLcode res, + bool premature) +{ + struct connectdata *conn = data->conn; + struct ldapreqinfo *lr = data->req.p.ldap; + + (void)res; + (void)premature; + + if(lr) { + /* if there was a search in progress, abandon it */ + if(lr->msgid) { + struct ldapconninfo *li = conn->proto.ldapc; + ldap_abandon_ext(li->ld, lr->msgid, NULL, NULL); + lr->msgid = 0; + } + data->req.p.ldap = NULL; + free(lr); + } + + return CURLE_OK; +} + +static CURLcode client_write(struct Curl_easy *data, + const char *prefix, size_t plen, + const char *value, size_t len, + const char *suffix, size_t slen) +{ + CURLcode result = CURLE_OK; + + if(prefix) { + /* If we have a zero-length value and the prefix ends with a space + separator, drop the latter. */ + if(!len && plen && prefix[plen - 1] == ' ') + plen--; + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) prefix, plen); + } + if(!result && value) { + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) value, len); + } + if(!result && suffix) { + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) suffix, slen); + } + return result; +} + +static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, + size_t len, CURLcode *err) +{ + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + struct ldapreqinfo *lr = data->req.p.ldap; + int rc; + LDAPMessage *msg = NULL; + BerElement *ber = NULL; + struct timeval tv = {0, 0}; + struct berval bv, *bvals; + int binary = 0; + CURLcode result = CURLE_AGAIN; + int code; + char *info = NULL; + + (void)len; + (void)buf; + (void)sockindex; + + rc = ldap_result(li->ld, lr->msgid, LDAP_MSG_ONE, &tv, &msg); + if(rc < 0) { + failf(data, "LDAP local: search ldap_result %s", ldap_err2string(rc)); + result = CURLE_RECV_ERROR; + } + + *err = result; + + /* error or timed out */ + if(!msg) + return -1; + + result = CURLE_OK; + + switch(ldap_msgtype(msg)) { + case LDAP_RES_SEARCH_RESULT: + lr->msgid = 0; + rc = ldap_parse_result(li->ld, msg, &code, NULL, &info, NULL, NULL, 0); + if(rc) { + failf(data, "LDAP local: search ldap_parse_result %s", + ldap_err2string(rc)); + result = CURLE_LDAP_SEARCH_FAILED; + break; + } + + /* store the latest code for later retrieval */ + data->info.httpcode = code; + + switch(code) { + case LDAP_SIZELIMIT_EXCEEDED: + infof(data, "There are more than %d entries", lr->nument); + /* FALLTHROUGH */ + case LDAP_SUCCESS: + data->req.size = data->req.bytecount; + break; + default: + failf(data, "LDAP remote: search failed %s %s", ldap_err2string(code), + info ? info : ""); + result = CURLE_LDAP_SEARCH_FAILED; + break; + } + if(info) + ldap_memfree(info); + break; + case LDAP_RES_SEARCH_ENTRY: + lr->nument++; + rc = ldap_get_dn_ber(li->ld, msg, &ber, &bv); + if(rc < 0) { + result = CURLE_RECV_ERROR; + break; + } + + result = client_write(data, STRCONST("DN: "), bv.bv_val, bv.bv_len, + STRCONST("\n")); + if(result) + break; + + for(rc = ldap_get_attribute_ber(li->ld, msg, ber, &bv, &bvals); + rc == LDAP_SUCCESS; + rc = ldap_get_attribute_ber(li->ld, msg, ber, &bv, &bvals)) { + int i; + + if(!bv.bv_val) + break; + + if(!bvals) { + result = client_write(data, STRCONST("\t"), bv.bv_val, bv.bv_len, + STRCONST(":\n")); + if(result) + break; + continue; + } + + binary = bv.bv_len > 7 && + !strncmp(bv.bv_val + bv.bv_len - 7, ";binary", 7); + + for(i = 0; bvals[i].bv_val != NULL; i++) { + int binval = 0; + + result = client_write(data, STRCONST("\t"), bv.bv_val, bv.bv_len, + STRCONST(":")); + if(result) + break; + + if(!binary) { + /* check for leading or trailing whitespace */ + if(ISBLANK(bvals[i].bv_val[0]) || + ISBLANK(bvals[i].bv_val[bvals[i].bv_len - 1])) + binval = 1; + else { + /* check for unprintable characters */ + unsigned int j; + for(j = 0; j < bvals[i].bv_len; j++) + if(!ISPRINT(bvals[i].bv_val[j])) { + binval = 1; + break; + } + } + } + if(binary || binval) { + char *val_b64 = NULL; + size_t val_b64_sz = 0; + + /* Binary value, encode to base64. */ + if(bvals[i].bv_len) + result = Curl_base64_encode(bvals[i].bv_val, bvals[i].bv_len, + &val_b64, &val_b64_sz); + if(!result) + result = client_write(data, STRCONST(": "), val_b64, val_b64_sz, + STRCONST("\n")); + free(val_b64); + } + else + result = client_write(data, STRCONST(" "), + bvals[i].bv_val, bvals[i].bv_len, + STRCONST("\n")); + if(result) + break; + } + + ber_memfree(bvals); + bvals = NULL; + if(!result) + result = client_write(data, STRCONST("\n"), NULL, 0, NULL, 0); + if(result) + break; + } + + ber_free(ber, 0); + + if(!result) + result = client_write(data, STRCONST("\n"), NULL, 0, NULL, 0); + if(!result) + result = CURLE_AGAIN; + break; + } + + ldap_msgfree(msg); + *err = result; + return result? -1: 0; +} + +#ifdef USE_SSL +static int +ldapsb_tls_setup(Sockbuf_IO_Desc *sbiod, void *arg) +{ + sbiod->sbiod_pvt = arg; + return 0; +} + +static int +ldapsb_tls_remove(Sockbuf_IO_Desc *sbiod) +{ + sbiod->sbiod_pvt = NULL; + return 0; +} + +/* We don't need to do anything because libcurl does it already */ +static int +ldapsb_tls_close(Sockbuf_IO_Desc *sbiod) +{ + (void)sbiod; + return 0; +} + +static int +ldapsb_tls_ctrl(Sockbuf_IO_Desc *sbiod, int opt, void *arg) +{ + (void)arg; + if(opt == LBER_SB_OPT_DATA_READY) { + struct Curl_easy *data = sbiod->sbiod_pvt; + return Curl_conn_data_pending(data, FIRSTSOCKET); + } + return 0; +} + +static ber_slen_t +ldapsb_tls_read(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct Curl_easy *data = sbiod->sbiod_pvt; + ber_slen_t ret = 0; + if(data) { + struct connectdata *conn = data->conn; + if(conn) { + struct ldapconninfo *li = conn->proto.ldapc; + CURLcode err = CURLE_RECV_ERROR; + + ret = (li->recv)(data, FIRSTSOCKET, buf, len, &err); + if(ret < 0 && err == CURLE_AGAIN) { + SET_SOCKERRNO(EWOULDBLOCK); + } + } + } + return ret; +} + +static ber_slen_t +ldapsb_tls_write(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct Curl_easy *data = sbiod->sbiod_pvt; + ber_slen_t ret = 0; + if(data) { + struct connectdata *conn = data->conn; + if(conn) { + struct ldapconninfo *li = conn->proto.ldapc; + CURLcode err = CURLE_SEND_ERROR; + ret = (li->send)(data, FIRSTSOCKET, buf, len, &err); + if(ret < 0 && err == CURLE_AGAIN) { + SET_SOCKERRNO(EWOULDBLOCK); + } + } + } + return ret; +} + +static Sockbuf_IO ldapsb_tls = +{ + ldapsb_tls_setup, + ldapsb_tls_remove, + ldapsb_tls_ctrl, + ldapsb_tls_read, + ldapsb_tls_write, + ldapsb_tls_close +}; +#endif /* USE_SSL */ + +#endif /* !CURL_DISABLE_LDAP && USE_OPENLDAP */ diff --git a/Utilities/cmcurl/lib/parsedate.c b/Utilities/cmcurl/lib/parsedate.c new file mode 100644 index 0000000..1a7195b --- /dev/null +++ b/Utilities/cmcurl/lib/parsedate.c @@ -0,0 +1,644 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + A brief summary of the date string formats this parser groks: + + 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 + + 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 + + without the time zone: + + 06 Nov 1994 08:49:37 + 06-Nov-94 08:49:37 + + 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) + + time left out: + + 1994 Nov 6 + 06-Nov-94 + Sun Nov 6 94 + + unusual separators: + + 1994.Nov.6 + Sun/Nov/6/94/GMT + + commonly used time zone names: + + Sun, 06 Nov 1994 08:49:37 CET + 06 Nov 1994 08:49:37 EST + + time zones specified using RFC822 style: + + Sun, 12 Sep 2004 15:05:58 -0700 + Sat, 11 Sep 2004 21:32:11 +0200 + + compact numerical date strings: + + 20040912 15:05:58 -0700 + 20040911 +0200 + +*/ + +#include "curl_setup.h" + +#include <limits.h> + +#include <curl/curl.h> +#include "strcase.h" +#include "warnless.h" +#include "parsedate.h" + +/* + * parsedate() + * + * Returns: + * + * PARSEDATE_OK - a fine conversion + * PARSEDATE_FAIL - failed to convert + * PARSEDATE_LATER - time overflow at the far end of time_t + * PARSEDATE_SOONER - time underflow at the low end of time_t + */ + +static int parsedate(const char *date, time_t *output); + +#define PARSEDATE_OK 0 +#define PARSEDATE_FAIL -1 +#define PARSEDATE_LATER 1 +#define PARSEDATE_SOONER 2 + +#if !defined(CURL_DISABLE_PARSEDATE) || !defined(CURL_DISABLE_FTP) || \ + !defined(CURL_DISABLE_FILE) +/* These names are also used by FTP and FILE code */ +const char * const Curl_wkday[] = +{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; +const char * const Curl_month[]= +{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; +#endif + +#ifndef CURL_DISABLE_PARSEDATE +static const char * const weekday[] = +{ "Monday", "Tuesday", "Wednesday", "Thursday", + "Friday", "Saturday", "Sunday" }; + +struct tzinfo { + char name[5]; + int offset; /* +/- in minutes */ +}; + +/* Here's a bunch of frequently used time zone names. These were supported + by the old getdate parser. */ +#define tDAYZONE -60 /* offset for daylight savings time */ +static const struct tzinfo tz[]= { + {"GMT", 0}, /* Greenwich Mean */ + {"UT", 0}, /* Universal Time */ + {"UTC", 0}, /* Universal (Coordinated) */ + {"WET", 0}, /* Western European */ + {"BST", 0 tDAYZONE}, /* British Summer */ + {"WAT", 60}, /* West Africa */ + {"AST", 240}, /* Atlantic Standard */ + {"ADT", 240 tDAYZONE}, /* Atlantic Daylight */ + {"EST", 300}, /* Eastern Standard */ + {"EDT", 300 tDAYZONE}, /* Eastern Daylight */ + {"CST", 360}, /* Central Standard */ + {"CDT", 360 tDAYZONE}, /* Central Daylight */ + {"MST", 420}, /* Mountain Standard */ + {"MDT", 420 tDAYZONE}, /* Mountain Daylight */ + {"PST", 480}, /* Pacific Standard */ + {"PDT", 480 tDAYZONE}, /* Pacific Daylight */ + {"YST", 540}, /* Yukon Standard */ + {"YDT", 540 tDAYZONE}, /* Yukon Daylight */ + {"HST", 600}, /* Hawaii Standard */ + {"HDT", 600 tDAYZONE}, /* Hawaii Daylight */ + {"CAT", 600}, /* Central Alaska */ + {"AHST", 600}, /* Alaska-Hawaii Standard */ + {"NT", 660}, /* Nome */ + {"IDLW", 720}, /* International Date Line West */ + {"CET", -60}, /* Central European */ + {"MET", -60}, /* Middle European */ + {"MEWT", -60}, /* Middle European Winter */ + {"MEST", -60 tDAYZONE}, /* Middle European Summer */ + {"CEST", -60 tDAYZONE}, /* Central European Summer */ + {"MESZ", -60 tDAYZONE}, /* Middle European Summer */ + {"FWT", -60}, /* French Winter */ + {"FST", -60 tDAYZONE}, /* French Summer */ + {"EET", -120}, /* Eastern Europe, USSR Zone 1 */ + {"WAST", -420}, /* West Australian Standard */ + {"WADT", -420 tDAYZONE}, /* West Australian Daylight */ + {"CCT", -480}, /* China Coast, USSR Zone 7 */ + {"JST", -540}, /* Japan Standard, USSR Zone 8 */ + {"EAST", -600}, /* Eastern Australian Standard */ + {"EADT", -600 tDAYZONE}, /* Eastern Australian Daylight */ + {"GST", -600}, /* Guam Standard, USSR Zone 9 */ + {"NZT", -720}, /* New Zealand */ + {"NZST", -720}, /* New Zealand Standard */ + {"NZDT", -720 tDAYZONE}, /* New Zealand Daylight */ + {"IDLE", -720}, /* International Date Line East */ + /* Next up: Military timezone names. RFC822 allowed these, but (as noted in + RFC 1123) had their signs wrong. Here we use the correct signs to match + actual military usage. + */ + {"A", 1 * 60}, /* Alpha */ + {"B", 2 * 60}, /* Bravo */ + {"C", 3 * 60}, /* Charlie */ + {"D", 4 * 60}, /* Delta */ + {"E", 5 * 60}, /* Echo */ + {"F", 6 * 60}, /* Foxtrot */ + {"G", 7 * 60}, /* Golf */ + {"H", 8 * 60}, /* Hotel */ + {"I", 9 * 60}, /* India */ + /* "J", Juliet is not used as a timezone, to indicate the observer's local + time */ + {"K", 10 * 60}, /* Kilo */ + {"L", 11 * 60}, /* Lima */ + {"M", 12 * 60}, /* Mike */ + {"N", -1 * 60}, /* November */ + {"O", -2 * 60}, /* Oscar */ + {"P", -3 * 60}, /* Papa */ + {"Q", -4 * 60}, /* Quebec */ + {"R", -5 * 60}, /* Romeo */ + {"S", -6 * 60}, /* Sierra */ + {"T", -7 * 60}, /* Tango */ + {"U", -8 * 60}, /* Uniform */ + {"V", -9 * 60}, /* Victor */ + {"W", -10 * 60}, /* Whiskey */ + {"X", -11 * 60}, /* X-ray */ + {"Y", -12 * 60}, /* Yankee */ + {"Z", 0}, /* Zulu, zero meridian, a.k.a. UTC */ +}; + +/* returns: + -1 no day + 0 monday - 6 sunday +*/ + +static int checkday(const char *check, size_t len) +{ + int i; + const char * const *what; + if(len > 3) + what = &weekday[0]; + else if(len == 3) + what = &Curl_wkday[0]; + else + return -1; /* too short */ + for(i = 0; i<7; i++) { + size_t ilen = strlen(what[0]); + if((ilen == len) && + strncasecompare(check, what[0], len)) + return i; + what++; + } + return -1; +} + +static int checkmonth(const char *check, size_t len) +{ + int i; + const char * const *what = &Curl_month[0]; + if(len != 3) + return -1; /* not a month */ + + for(i = 0; i<12; i++) { + if(strncasecompare(check, what[0], 3)) + return i; + what++; + } + return -1; /* return the offset or -1, no real offset is -1 */ +} + +/* return the time zone offset between GMT and the input one, in number + of seconds or -1 if the timezone wasn't found/legal */ + +static int checktz(const char *check, size_t len) +{ + unsigned int i; + const struct tzinfo *what = tz; + if(len > 4) /* longer than any valid timezone */ + return -1; + + for(i = 0; i< sizeof(tz)/sizeof(tz[0]); i++) { + size_t ilen = strlen(what->name); + if((ilen == len) && + strncasecompare(check, what->name, len)) + return what->offset*60; + what++; + } + return -1; +} + +static void skip(const char **date) +{ + /* skip everything that aren't letters or digits */ + while(**date && !ISALNUM(**date)) + (*date)++; +} + +enum assume { + DATE_MDAY, + DATE_YEAR, + DATE_TIME +}; + +/* + * time2epoch: time stamp to seconds since epoch in GMT time zone. Similar to + * mktime but for GMT only. + */ +static time_t time2epoch(int sec, int min, int hour, + int mday, int mon, int year) +{ + static const int month_days_cumulative [12] = + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + int leap_days = year - (mon <= 1); + leap_days = ((leap_days / 4) - (leap_days / 100) + (leap_days / 400) + - (1969 / 4) + (1969 / 100) - (1969 / 400)); + return ((((time_t) (year - 1970) * 365 + + leap_days + month_days_cumulative[mon] + mday - 1) * 24 + + hour) * 60 + min) * 60 + sec; +} + +/* Returns the value of a single-digit or two-digit decimal number, return + then pointer to after the number. The 'date' pointer is known to point to a + digit. */ +static int oneortwodigit(const char *date, const char **endp) +{ + int num = date[0] - '0'; + if(ISDIGIT(date[1])) { + *endp = &date[2]; + return num*10 + (date[1] - '0'); + } + *endp = &date[1]; + return num; +} + + +/* HH:MM:SS or HH:MM and accept single-digits too */ +static bool match_time(const char *date, + int *h, int *m, int *s, char **endp) +{ + const char *p; + int hh, mm, ss = 0; + hh = oneortwodigit(date, &p); + if((hh < 24) && (*p == ':') && ISDIGIT(p[1])) { + mm = oneortwodigit(&p[1], &p); + if(mm < 60) { + if((*p == ':') && ISDIGIT(p[1])) { + ss = oneortwodigit(&p[1], &p); + if(ss <= 60) { + /* valid HH:MM:SS */ + goto match; + } + } + else { + /* valid HH:MM */ + goto match; + } + } + } + return FALSE; /* not a time string */ +match: + *h = hh; + *m = mm; + *s = ss; + *endp = (char *)p; + return TRUE; +} + +/* + * parsedate() + * + * Returns: + * + * PARSEDATE_OK - a fine conversion + * PARSEDATE_FAIL - failed to convert + * PARSEDATE_LATER - time overflow at the far end of time_t + * PARSEDATE_SOONER - time underflow at the low end of time_t + */ + +/* Wednesday is the longest name this parser knows about */ +#define NAME_LEN 12 + +static int parsedate(const char *date, time_t *output) +{ + time_t t = 0; + int wdaynum = -1; /* day of the week number, 0-6 (mon-sun) */ + int monnum = -1; /* month of the year number, 0-11 */ + int mdaynum = -1; /* day of month, 1 - 31 */ + int hournum = -1; + int minnum = -1; + int secnum = -1; + int yearnum = -1; + int tzoff = -1; + enum assume dignext = DATE_MDAY; + const char *indate = date; /* save the original pointer */ + int part = 0; /* max 6 parts */ + + while(*date && (part < 6)) { + bool found = FALSE; + + skip(&date); + + if(ISALPHA(*date)) { + /* a name coming up */ + size_t len = 0; + const char *p = date; + while(ISALPHA(*p) && (len < NAME_LEN)) { + p++; + len++; + } + + if(len != NAME_LEN) { + if(wdaynum == -1) { + wdaynum = checkday(date, len); + if(wdaynum != -1) + found = TRUE; + } + if(!found && (monnum == -1)) { + monnum = checkmonth(date, len); + if(monnum != -1) + found = TRUE; + } + + if(!found && (tzoff == -1)) { + /* this just must be a time zone string */ + tzoff = checktz(date, len); + if(tzoff != -1) + found = TRUE; + } + } + if(!found) + return PARSEDATE_FAIL; /* bad string */ + + date += len; + } + else if(ISDIGIT(*date)) { + /* a digit */ + int val; + char *end; + if((secnum == -1) && + match_time(date, &hournum, &minnum, &secnum, &end)) { + /* time stamp */ + date = end; + } + else { + long lval; + int error; + int old_errno; + + old_errno = errno; + errno = 0; + lval = strtol(date, &end, 10); + error = errno; + if(errno != old_errno) + errno = old_errno; + + if(error) + return PARSEDATE_FAIL; + +#if LONG_MAX != INT_MAX + if((lval > (long)INT_MAX) || (lval < (long)INT_MIN)) + return PARSEDATE_FAIL; +#endif + + val = curlx_sltosi(lval); + + if((tzoff == -1) && + ((end - date) == 4) && + (val <= 1400) && + (indate< date) && + ((date[-1] == '+' || date[-1] == '-'))) { + /* four digits and a value less than or equal to 1400 (to take into + account all sorts of funny time zone diffs) and it is preceded + with a plus or minus. This is a time zone indication. 1400 is + picked since +1300 is frequently used and +1400 is mentioned as + an edge number in the document "ISO C 200X Proposal: Timezone + Functions" at http://david.tribble.com/text/c0xtimezone.html If + anyone has a more authoritative source for the exact maximum time + zone offsets, please speak up! */ + found = TRUE; + tzoff = (val/100 * 60 + val%100)*60; + + /* the + and - prefix indicates the local time compared to GMT, + this we need their reversed math to get what we want */ + tzoff = date[-1]=='+'?-tzoff:tzoff; + } + + if(((end - date) == 8) && + (yearnum == -1) && + (monnum == -1) && + (mdaynum == -1)) { + /* 8 digits, no year, month or day yet. This is YYYYMMDD */ + found = TRUE; + yearnum = val/10000; + monnum = (val%10000)/100-1; /* month is 0 - 11 */ + mdaynum = val%100; + } + + if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) { + if((val > 0) && (val<32)) { + mdaynum = val; + found = TRUE; + } + dignext = DATE_YEAR; + } + + if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) { + yearnum = val; + found = TRUE; + if(yearnum < 100) { + if(yearnum > 70) + yearnum += 1900; + else + yearnum += 2000; + } + if(mdaynum == -1) + dignext = DATE_MDAY; + } + + if(!found) + return PARSEDATE_FAIL; + + date = end; + } + } + + part++; + } + + if(-1 == secnum) + secnum = minnum = hournum = 0; /* no time, make it zero */ + + if((-1 == mdaynum) || + (-1 == monnum) || + (-1 == yearnum)) + /* lacks vital info, fail */ + return PARSEDATE_FAIL; + +#ifdef HAVE_TIME_T_UNSIGNED + if(yearnum < 1970) { + /* only positive numbers cannot return earlier */ + *output = TIME_T_MIN; + return PARSEDATE_SOONER; + } +#endif + +#if (SIZEOF_TIME_T < 5) + +#ifdef HAVE_TIME_T_UNSIGNED + /* an unsigned 32 bit time_t can only hold dates to 2106 */ + if(yearnum > 2105) { + *output = TIME_T_MAX; + return PARSEDATE_LATER; + } +#else + /* a signed 32 bit time_t can only hold dates to the beginning of 2038 */ + if(yearnum > 2037) { + *output = TIME_T_MAX; + return PARSEDATE_LATER; + } + if(yearnum < 1903) { + *output = TIME_T_MIN; + return PARSEDATE_SOONER; + } +#endif + +#else + /* The Gregorian calendar was introduced 1582 */ + if(yearnum < 1583) + return PARSEDATE_FAIL; +#endif + + if((mdaynum > 31) || (monnum > 11) || + (hournum > 23) || (minnum > 59) || (secnum > 60)) + return PARSEDATE_FAIL; /* clearly an illegal date */ + + /* time2epoch() returns a time_t. time_t is often 32 bits, sometimes even on + architectures that feature 64 bit 'long' but ultimately time_t is the + correct data type to use. + */ + t = time2epoch(secnum, minnum, hournum, mdaynum, monnum, yearnum); + + /* Add the time zone diff between local time zone and GMT. */ + if(tzoff == -1) + tzoff = 0; + + if((tzoff > 0) && (t > TIME_T_MAX - tzoff)) { + *output = TIME_T_MAX; + return PARSEDATE_LATER; /* time_t overflow */ + } + + t += tzoff; + + *output = t; + + return PARSEDATE_OK; +} +#else +/* disabled */ +static int parsedate(const char *date, time_t *output) +{ + (void)date; + *output = 0; + return PARSEDATE_OK; /* a lie */ +} +#endif + +time_t curl_getdate(const char *p, const time_t *now) +{ + time_t parsed = -1; + int rc = parsedate(p, &parsed); + (void)now; /* legacy argument from the past that we ignore */ + + if(rc == PARSEDATE_OK) { + if(parsed == -1) + /* avoid returning -1 for a working scenario */ + parsed++; + return parsed; + } + /* everything else is fail */ + return -1; +} + +/* Curl_getdate_capped() differs from curl_getdate() in that this will return + TIME_T_MAX in case the parsed time value was too big, instead of an + error. */ + +time_t Curl_getdate_capped(const char *p) +{ + time_t parsed = -1; + int rc = parsedate(p, &parsed); + + switch(rc) { + case PARSEDATE_OK: + if(parsed == -1) + /* avoid returning -1 for a working scenario */ + parsed++; + return parsed; + case PARSEDATE_LATER: + /* this returns the maximum time value */ + return parsed; + default: + return -1; /* everything else is fail */ + } + /* UNREACHABLE */ +} + +/* + * Curl_gmtime() is a gmtime() replacement for portability. Do not use the + * gmtime_r() or gmtime() functions anywhere else but here. + * + */ + +CURLcode Curl_gmtime(time_t intime, struct tm *store) +{ + const struct tm *tm; +#ifdef HAVE_GMTIME_R + /* thread-safe version */ + tm = (struct tm *)gmtime_r(&intime, store); +#else + /* !checksrc! disable BANNEDFUNC 1 */ + tm = gmtime(&intime); + if(tm) + *store = *tm; /* copy the pointed struct to the local copy */ +#endif + + if(!tm) + return CURLE_BAD_FUNCTION_ARGUMENT; + return CURLE_OK; +} diff --git a/Utilities/cmcurl/lib/parsedate.h b/Utilities/cmcurl/lib/parsedate.h new file mode 100644 index 0000000..84c37f1 --- /dev/null +++ b/Utilities/cmcurl/lib/parsedate.h @@ -0,0 +1,38 @@ +#ifndef HEADER_CURL_PARSEDATE_H +#define HEADER_CURL_PARSEDATE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +extern const char * const Curl_wkday[7]; +extern const char * const Curl_month[12]; + +CURLcode Curl_gmtime(time_t intime, struct tm *store); + +/* Curl_getdate_capped() differs from curl_getdate() in that this will return + TIME_T_MAX in case the parsed time value was too big, instead of an + error. */ + +time_t Curl_getdate_capped(const char *p); + +#endif /* HEADER_CURL_PARSEDATE_H */ diff --git a/Utilities/cmcurl/lib/pingpong.c b/Utilities/cmcurl/lib/pingpong.c new file mode 100644 index 0000000..0081c9c --- /dev/null +++ b/Utilities/cmcurl/lib/pingpong.c @@ -0,0 +1,501 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * 'pingpong' is for generic back-and-forth support functions used by FTP, + * IMAP, POP3, SMTP and whatever more that likes them. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include "cfilters.h" +#include "sendf.h" +#include "select.h" +#include "progress.h" +#include "speedcheck.h" +#include "pingpong.h" +#include "multiif.h" +#include "vtls/vtls.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef USE_PINGPONG + +/* Returns timeout in ms. 0 or negative number means the timeout has already + triggered */ +timediff_t Curl_pp_state_timeout(struct Curl_easy *data, + struct pingpong *pp, bool disconnecting) +{ + struct connectdata *conn = data->conn; + timediff_t timeout_ms; /* in milliseconds */ + timediff_t response_time = (data->set.server_response_timeout)? + data->set.server_response_timeout: pp->response_time; + + /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine + remaining time, or use pp->response because SERVER_RESPONSE_TIMEOUT is + supposed to govern the response for any given server response, not for + the time from connect to the given server response. */ + + /* Without a requested timeout, we only wait 'response_time' seconds for the + full response to arrive before we bail out */ + timeout_ms = response_time - + Curl_timediff(Curl_now(), pp->response); /* spent time */ + + if(data->set.timeout && !disconnecting) { + /* if timeout is requested, find out how much remaining time we have */ + timediff_t timeout2_ms = data->set.timeout - /* timeout time */ + Curl_timediff(Curl_now(), conn->now); /* spent time */ + + /* pick the lowest number */ + timeout_ms = CURLMIN(timeout_ms, timeout2_ms); + } + + return timeout_ms; +} + +/* + * Curl_pp_statemach() + */ +CURLcode Curl_pp_statemach(struct Curl_easy *data, + struct pingpong *pp, bool block, + bool disconnecting) +{ + struct connectdata *conn = data->conn; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int rc; + timediff_t interval_ms; + timediff_t timeout_ms = Curl_pp_state_timeout(data, pp, disconnecting); + CURLcode result = CURLE_OK; + + if(timeout_ms <= 0) { + failf(data, "server response timeout"); + return CURLE_OPERATION_TIMEDOUT; /* already too little time */ + } + + if(block) { + interval_ms = 1000; /* use 1 second timeout intervals */ + if(timeout_ms < interval_ms) + interval_ms = timeout_ms; + } + else + interval_ms = 0; /* immediate */ + + if(Curl_conn_data_pending(data, FIRSTSOCKET)) + rc = 1; + else if(Curl_pp_moredata(pp)) + /* We are receiving and there is data in the cache so just read it */ + rc = 1; + else if(!pp->sendleft && Curl_conn_data_pending(data, FIRSTSOCKET)) + /* We are receiving and there is data ready in the SSL library */ + rc = 1; + else + rc = Curl_socket_check(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */ + CURL_SOCKET_BAD, + pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */ + interval_ms); + + if(block) { + /* if we didn't wait, we don't have to spend time on this now */ + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, Curl_now()); + + if(result) + return result; + } + + if(rc == -1) { + failf(data, "select/poll error"); + result = CURLE_OUT_OF_MEMORY; + } + else if(rc) + result = pp->statemachine(data, data->conn); + + return result; +} + +/* initialize stuff to prepare for reading a fresh new response */ +void Curl_pp_init(struct Curl_easy *data, struct pingpong *pp) +{ + DEBUGASSERT(data); + pp->nread_resp = 0; + pp->linestart_resp = data->state.buffer; + pp->pending_resp = TRUE; + pp->response = Curl_now(); /* start response time-out now! */ +} + +/* setup for the coming transfer */ +void Curl_pp_setup(struct pingpong *pp) +{ + Curl_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD); +} + +/*********************************************************************** + * + * Curl_pp_vsendf() + * + * Send the formatted string as a command to a pingpong server. Note that + * the string should not have any CRLF appended, as this function will + * append the necessary things itself. + * + * made to never block + */ +CURLcode Curl_pp_vsendf(struct Curl_easy *data, + struct pingpong *pp, + const char *fmt, + va_list args) +{ + ssize_t bytes_written = 0; + size_t write_len; + char *s; + CURLcode result; + struct connectdata *conn = data->conn; + +#ifdef HAVE_GSSAPI + enum protection_level data_sec; +#endif + + DEBUGASSERT(pp->sendleft == 0); + DEBUGASSERT(pp->sendsize == 0); + DEBUGASSERT(pp->sendthis == NULL); + + if(!conn) + /* can't send without a connection! */ + return CURLE_SEND_ERROR; + + Curl_dyn_reset(&pp->sendbuf); + result = Curl_dyn_vaddf(&pp->sendbuf, fmt, args); + if(result) + return result; + + /* append CRLF */ + result = Curl_dyn_addn(&pp->sendbuf, "\r\n", 2); + if(result) + return result; + + write_len = Curl_dyn_len(&pp->sendbuf); + s = Curl_dyn_ptr(&pp->sendbuf); + Curl_pp_init(data, pp); + +#ifdef HAVE_GSSAPI + conn->data_prot = PROT_CMD; +#endif + result = Curl_nwrite(data, FIRSTSOCKET, s, write_len, &bytes_written); + if(result) + return result; +#ifdef HAVE_GSSAPI + data_sec = conn->data_prot; + DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST); + conn->data_prot = (unsigned char)data_sec; +#endif + + Curl_debug(data, CURLINFO_HEADER_OUT, s, (size_t)bytes_written); + + if(bytes_written != (ssize_t)write_len) { + /* the whole chunk was not sent, keep it around and adjust sizes */ + pp->sendthis = s; + pp->sendsize = write_len; + pp->sendleft = write_len - bytes_written; + } + else { + pp->sendthis = NULL; + pp->sendleft = pp->sendsize = 0; + pp->response = Curl_now(); + } + + return CURLE_OK; +} + + +/*********************************************************************** + * + * Curl_pp_sendf() + * + * Send the formatted string as a command to a pingpong server. Note that + * the string should not have any CRLF appended, as this function will + * append the necessary things itself. + * + * made to never block + */ +CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp, + const char *fmt, ...) +{ + CURLcode result; + va_list ap; + va_start(ap, fmt); + + result = Curl_pp_vsendf(data, pp, fmt, ap); + + va_end(ap); + + return result; +} + +/* + * Curl_pp_readresp() + * + * Reads a piece of a server response. + */ +CURLcode Curl_pp_readresp(struct Curl_easy *data, + curl_socket_t sockfd, + struct pingpong *pp, + int *code, /* return the server code if done */ + size_t *size) /* size of the response */ +{ + ssize_t perline; /* count bytes per line */ + bool keepon = TRUE; + ssize_t gotbytes; + char *ptr; + struct connectdata *conn = data->conn; + char * const buf = data->state.buffer; + CURLcode result = CURLE_OK; + + *code = 0; /* 0 for errors or not done */ + *size = 0; + + ptr = buf + pp->nread_resp; + + /* number of bytes in the current line, so far */ + perline = (ssize_t)(ptr-pp->linestart_resp); + + while((pp->nread_resp < (size_t)data->set.buffer_size) && + (keepon && !result)) { + + if(pp->cache) { + /* we had data in the "cache", copy that instead of doing an actual + * read + * + * pp->cache_size is cast to ssize_t here. This should be safe, because + * it would have been populated with something of size int to begin + * with, even though its datatype may be larger than an int. + */ + if((ptr + pp->cache_size) > (buf + data->set.buffer_size + 1)) { + failf(data, "cached response data too big to handle"); + return CURLE_WEIRD_SERVER_REPLY; + } + memcpy(ptr, pp->cache, pp->cache_size); + gotbytes = (ssize_t)pp->cache_size; + free(pp->cache); /* free the cache */ + pp->cache = NULL; /* clear the pointer */ + pp->cache_size = 0; /* zero the size just in case */ + } + else { +#ifdef HAVE_GSSAPI + enum protection_level prot = conn->data_prot; + conn->data_prot = PROT_CLEAR; +#endif + DEBUGASSERT((ptr + data->set.buffer_size - pp->nread_resp) <= + (buf + data->set.buffer_size + 1)); + result = Curl_read(data, sockfd, ptr, + data->set.buffer_size - pp->nread_resp, + &gotbytes); +#ifdef HAVE_GSSAPI + DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); + conn->data_prot = (unsigned char)prot; +#endif + if(result == CURLE_AGAIN) + return CURLE_OK; /* return */ + + if(result) + /* Set outer result variable to this error. */ + keepon = FALSE; + } + + if(!keepon) + ; + else if(gotbytes <= 0) { + keepon = FALSE; + result = CURLE_RECV_ERROR; + failf(data, "response reading failed (errno: %d)", SOCKERRNO); + } + else { + /* we got a whole chunk of data, which can be anything from one + * byte to a set of lines and possible just a piece of the last + * line */ + ssize_t i; + ssize_t clipamount = 0; + bool restart = FALSE; + + data->req.headerbytecount += (unsigned int)gotbytes; + + pp->nread_resp += gotbytes; + for(i = 0; i < gotbytes; ptr++, i++) { + perline++; + if(*ptr == '\n') { + /* a newline is CRLF in pp-talk, so the CR is ignored as + the line isn't really terminated until the LF comes */ + + /* output debug output if that is requested */ +#ifdef HAVE_GSSAPI + if(!conn->sec_complete) +#endif + Curl_debug(data, CURLINFO_HEADER_IN, + pp->linestart_resp, (size_t)perline); + + /* + * We pass all response-lines to the callback function registered + * for "headers". The response lines can be seen as a kind of + * headers. + */ + result = Curl_client_write(data, CLIENTWRITE_INFO, + pp->linestart_resp, perline); + if(result) + return result; + + if(pp->endofresp(data, conn, pp->linestart_resp, perline, code)) { + /* This is the end of the last line, copy the last line to the + start of the buffer and null-terminate, for old times sake */ + size_t n = ptr - pp->linestart_resp; + memmove(buf, pp->linestart_resp, n); + buf[n] = 0; /* null-terminate */ + keepon = FALSE; + pp->linestart_resp = ptr + 1; /* advance pointer */ + i++; /* skip this before getting out */ + + *size = pp->nread_resp; /* size of the response */ + pp->nread_resp = 0; /* restart */ + break; + } + perline = 0; /* line starts over here */ + pp->linestart_resp = ptr + 1; + } + } + + if(!keepon && (i != gotbytes)) { + /* We found the end of the response lines, but we didn't parse the + full chunk of data we have read from the server. We therefore need + to store the rest of the data to be checked on the next invoke as + it may actually contain another end of response already! */ + clipamount = gotbytes - i; + restart = TRUE; + DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing " + "server response left", + (int)clipamount)); + } + else if(keepon) { + + if((perline == gotbytes) && + (gotbytes > (ssize_t)data->set.buffer_size/2)) { + /* We got an excessive line without newlines and we need to deal + with it. We keep the first bytes of the line then we throw + away the rest. */ + infof(data, "Excessive server response line length received, " + "%zd bytes. Stripping", gotbytes); + restart = TRUE; + + /* we keep 40 bytes since all our pingpong protocols are only + interested in the first piece */ + clipamount = 40; + } + else if(pp->nread_resp > (size_t)data->set.buffer_size/2) { + /* We got a large chunk of data and there's potentially still + trailing data to take care of, so we put any such part in the + "cache", clear the buffer to make space and restart. */ + clipamount = perline; + restart = TRUE; + } + } + else if(i == gotbytes) + restart = TRUE; + + if(clipamount) { + pp->cache_size = clipamount; + pp->cache = malloc(pp->cache_size); + if(pp->cache) + memcpy(pp->cache, pp->linestart_resp, pp->cache_size); + else + return CURLE_OUT_OF_MEMORY; + } + if(restart) { + /* now reset a few variables to start over nicely from the start of + the big buffer */ + pp->nread_resp = 0; /* start over from scratch in the buffer */ + ptr = pp->linestart_resp = buf; + perline = 0; + } + + } /* there was data */ + + } /* while there's buffer left and loop is requested */ + + pp->pending_resp = FALSE; + + return result; +} + +int Curl_pp_getsock(struct Curl_easy *data, + struct pingpong *pp, curl_socket_t *socks) +{ + struct connectdata *conn = data->conn; + socks[0] = conn->sock[FIRSTSOCKET]; + + if(pp->sendleft) { + /* write mode */ + return GETSOCK_WRITESOCK(0); + } + + /* read mode */ + return GETSOCK_READSOCK(0); +} + +CURLcode Curl_pp_flushsend(struct Curl_easy *data, + struct pingpong *pp) +{ + /* we have a piece of a command still left to send */ + ssize_t written; + CURLcode result = Curl_nwrite(data, FIRSTSOCKET, + pp->sendthis + pp->sendsize - pp->sendleft, + pp->sendleft, &written); + if(result) + return result; + + if(written != (ssize_t)pp->sendleft) { + /* only a fraction was sent */ + pp->sendleft -= written; + } + else { + pp->sendthis = NULL; + pp->sendleft = pp->sendsize = 0; + pp->response = Curl_now(); + } + return CURLE_OK; +} + +CURLcode Curl_pp_disconnect(struct pingpong *pp) +{ + Curl_dyn_free(&pp->sendbuf); + Curl_safefree(pp->cache); + return CURLE_OK; +} + +bool Curl_pp_moredata(struct pingpong *pp) +{ + return (!pp->sendleft && pp->cache && pp->nread_resp < pp->cache_size) ? + TRUE : FALSE; +} + +#endif diff --git a/Utilities/cmcurl/lib/pingpong.h b/Utilities/cmcurl/lib/pingpong.h new file mode 100644 index 0000000..80d3f77 --- /dev/null +++ b/Utilities/cmcurl/lib/pingpong.h @@ -0,0 +1,164 @@ +#ifndef HEADER_CURL_PINGPONG_H +#define HEADER_CURL_PINGPONG_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_FTP) || \ + !defined(CURL_DISABLE_POP3) || !defined(CURL_DISABLE_SMTP) +#define USE_PINGPONG +#endif + +/* forward-declaration, this is defined in urldata.h */ +struct connectdata; + +typedef enum { + PPTRANSFER_BODY, /* yes do transfer a body */ + PPTRANSFER_INFO, /* do still go through to get info/headers */ + PPTRANSFER_NONE /* don't get anything and don't get info */ +} curl_pp_transfer; + +/* + * 'pingpong' is the generic struct used for protocols doing server<->client + * conversations in a back-and-forth style such as FTP, IMAP, POP3, SMTP etc. + * + * It holds response cache and non-blocking sending data. + */ +struct pingpong { + char *cache; /* data cache between getresponse()-calls */ + size_t cache_size; /* size of cache in bytes */ + size_t nread_resp; /* number of bytes currently read of a server response */ + char *linestart_resp; /* line start pointer for the server response + reader function */ + bool pending_resp; /* set TRUE when a server response is pending or in + progress, and is cleared once the last response is + read */ + char *sendthis; /* allocated pointer to a buffer that is to be sent to the + server */ + size_t sendleft; /* number of bytes left to send from the sendthis buffer */ + size_t sendsize; /* total size of the sendthis buffer */ + struct curltime response; /* set to Curl_now() when a command has been sent + off, used to time-out response reading */ + timediff_t response_time; /* When no timeout is given, this is the amount of + milliseconds we await for a server response. */ + struct dynbuf sendbuf; + + /* Function pointers the protocols MUST implement and provide for the + pingpong layer to function */ + + CURLcode (*statemachine)(struct Curl_easy *data, struct connectdata *conn); + bool (*endofresp)(struct Curl_easy *data, struct connectdata *conn, + char *ptr, size_t len, int *code); +}; + +#define PINGPONG_SETUP(pp,s,e) \ + do { \ + pp->response_time = RESP_TIMEOUT; \ + pp->statemachine = s; \ + pp->endofresp = e; \ + } while(0) + +/* + * Curl_pp_statemach() + * + * called repeatedly until done. Set 'wait' to make it wait a while on the + * socket if there's no traffic. + */ +CURLcode Curl_pp_statemach(struct Curl_easy *data, struct pingpong *pp, + bool block, bool disconnecting); + +/* initialize stuff to prepare for reading a fresh new response */ +void Curl_pp_init(struct Curl_easy *data, struct pingpong *pp); + +/* setup for the transfer */ +void Curl_pp_setup(struct pingpong *pp); + +/* Returns timeout in ms. 0 or negative number means the timeout has already + triggered */ +timediff_t Curl_pp_state_timeout(struct Curl_easy *data, + struct pingpong *pp, bool disconnecting); + + +/*********************************************************************** + * + * Curl_pp_sendf() + * + * Send the formatted string as a command to a pingpong server. Note that + * the string should not have any CRLF appended, as this function will + * append the necessary things itself. + * + * made to never block + */ +CURLcode Curl_pp_sendf(struct Curl_easy *data, + struct pingpong *pp, + const char *fmt, ...); + +/*********************************************************************** + * + * Curl_pp_vsendf() + * + * Send the formatted string as a command to a pingpong server. Note that + * the string should not have any CRLF appended, as this function will + * append the necessary things itself. + * + * made to never block + */ +CURLcode Curl_pp_vsendf(struct Curl_easy *data, + struct pingpong *pp, + const char *fmt, + va_list args); + +/* + * Curl_pp_readresp() + * + * Reads a piece of a server response. + */ +CURLcode Curl_pp_readresp(struct Curl_easy *data, + curl_socket_t sockfd, + struct pingpong *pp, + int *code, /* return the server code if done */ + size_t *size); /* size of the response */ + + +CURLcode Curl_pp_flushsend(struct Curl_easy *data, + struct pingpong *pp); + +/* call this when a pingpong connection is disconnected */ +CURLcode Curl_pp_disconnect(struct pingpong *pp); + +int Curl_pp_getsock(struct Curl_easy *data, struct pingpong *pp, + curl_socket_t *socks); + + +/*********************************************************************** + * + * Curl_pp_moredata() + * + * Returns whether there are still more data in the cache and so a call + * to Curl_pp_readresp() will not block. + */ +bool Curl_pp_moredata(struct pingpong *pp); + +#endif /* HEADER_CURL_PINGPONG_H */ diff --git a/Utilities/cmcurl/lib/pop3.c b/Utilities/cmcurl/lib/pop3.c new file mode 100644 index 0000000..3e0f20a --- /dev/null +++ b/Utilities/cmcurl/lib/pop3.c @@ -0,0 +1,1587 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC1734 POP3 Authentication + * RFC1939 POP3 protocol + * RFC2195 CRAM-MD5 authentication + * RFC2384 POP URL Scheme + * RFC2449 POP3 Extension Mechanism + * RFC2595 Using TLS with IMAP, POP3 and ACAP + * RFC2831 DIGEST-MD5 authentication + * RFC4422 Simple Authentication and Security Layer (SASL) + * RFC4616 PLAIN authentication + * RFC4752 The Kerberos V5 ("GSSAPI") SASL Mechanism + * RFC5034 POP3 SASL Authentication Mechanism + * RFC6749 OAuth 2.0 Authorization Framework + * RFC8314 Use of TLS for Email Submission and Access + * Draft LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt> + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_POP3 + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include <curl/curl.h> +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "progress.h" +#include "transfer.h" +#include "escape.h" +#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "socks.h" +#include "pop3.h" +#include "strtoofft.h" +#include "strcase.h" +#include "vtls/vtls.h" +#include "cfilters.h" +#include "connect.h" +#include "select.h" +#include "multiif.h" +#include "url.h" +#include "bufref.h" +#include "curl_sasl.h" +#include "curl_md5.h" +#include "warnless.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* Local API functions */ +static CURLcode pop3_regular_transfer(struct Curl_easy *data, bool *done); +static CURLcode pop3_do(struct Curl_easy *data, bool *done); +static CURLcode pop3_done(struct Curl_easy *data, CURLcode status, + bool premature); +static CURLcode pop3_connect(struct Curl_easy *data, bool *done); +static CURLcode pop3_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead); +static CURLcode pop3_multi_statemach(struct Curl_easy *data, bool *done); +static int pop3_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); +static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done); +static CURLcode pop3_setup_connection(struct Curl_easy *data, + struct connectdata *conn); +static CURLcode pop3_parse_url_options(struct connectdata *conn); +static CURLcode pop3_parse_url_path(struct Curl_easy *data); +static CURLcode pop3_parse_custom_request(struct Curl_easy *data); +static CURLcode pop3_perform_auth(struct Curl_easy *data, const char *mech, + const struct bufref *initresp); +static CURLcode pop3_continue_auth(struct Curl_easy *data, const char *mech, + const struct bufref *resp); +static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech); +static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out); + +/* + * POP3 protocol handler. + */ + +const struct Curl_handler Curl_handler_pop3 = { + "POP3", /* scheme */ + pop3_setup_connection, /* setup_connection */ + pop3_do, /* do_it */ + pop3_done, /* done */ + ZERO_NULL, /* do_more */ + pop3_connect, /* connect_it */ + pop3_multi_statemach, /* connecting */ + pop3_doing, /* doing */ + pop3_getsock, /* proto_getsock */ + pop3_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + pop3_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_POP3, /* defport */ + CURLPROTO_POP3, /* protocol */ + CURLPROTO_POP3, /* family */ + PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */ + PROTOPT_URLOPTIONS +}; + +#ifdef USE_SSL +/* + * POP3S protocol handler. + */ + +const struct Curl_handler Curl_handler_pop3s = { + "POP3S", /* scheme */ + pop3_setup_connection, /* setup_connection */ + pop3_do, /* do_it */ + pop3_done, /* done */ + ZERO_NULL, /* do_more */ + pop3_connect, /* connect_it */ + pop3_multi_statemach, /* connecting */ + pop3_doing, /* doing */ + pop3_getsock, /* proto_getsock */ + pop3_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + pop3_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_POP3S, /* defport */ + CURLPROTO_POP3S, /* protocol */ + CURLPROTO_POP3, /* family */ + PROTOPT_CLOSEACTION | PROTOPT_SSL + | PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS /* flags */ +}; +#endif + +/* SASL parameters for the pop3 protocol */ +static const struct SASLproto saslpop3 = { + "pop", /* The service name */ + pop3_perform_auth, /* Send authentication command */ + 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 */ + '*', /* Code received when continuation is expected */ + '+', /* Code to receive upon authentication success */ + SASL_AUTH_DEFAULT, /* Default mechanisms */ + SASL_FLAG_BASE64 /* Configuration flags */ +}; + +#ifdef USE_SSL +static void pop3_to_pop3s(struct connectdata *conn) +{ + /* Change the connection handler */ + conn->handler = &Curl_handler_pop3s; + + /* Set the connection's upgraded to TLS flag */ + conn->bits.tls_upgraded = TRUE; +} +#else +#define pop3_to_pop3s(x) Curl_nop_stmt +#endif + +/*********************************************************************** + * + * pop3_endofresp() + * + * Checks for an ending POP3 status code at the start of the given string, but + * also detects the APOP timestamp from the server greeting and various + * capabilities from the CAPA response including the supported authentication + * types and allowed SASL mechanisms. + */ +static bool pop3_endofresp(struct Curl_easy *data, struct connectdata *conn, + char *line, size_t len, int *resp) +{ + struct pop3_conn *pop3c = &conn->proto.pop3c; + (void)data; + + /* Do we have an error response? */ + if(len >= 4 && !memcmp("-ERR", line, 4)) { + *resp = '-'; + + return TRUE; + } + + /* Are we processing CAPA command responses? */ + if(pop3c->state == POP3_CAPA) { + /* Do we have the terminating line? */ + if(len >= 1 && line[0] == '.') + /* Treat the response as a success */ + *resp = '+'; + else + /* Treat the response as an untagged continuation */ + *resp = '*'; + + return TRUE; + } + + /* Do we have a success response? */ + if(len >= 3 && !memcmp("+OK", line, 3)) { + *resp = '+'; + + return TRUE; + } + + /* Do we have a continuation response? */ + if(len >= 1 && line[0] == '+') { + *resp = '*'; + + return TRUE; + } + + return FALSE; /* Nothing for us */ +} + +/*********************************************************************** + * + * pop3_get_message() + * + * Gets the authentication message from the response buffer. + */ +static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out) +{ + char *message = data->state.buffer; + size_t len = strlen(message); + + if(len > 2) { + /* Find the start of the message */ + len -= 2; + for(message += 2; *message == ' ' || *message == '\t'; message++, len--) + ; + + /* Find the end of the message */ + while(len--) + if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' && + message[len] != '\t') + break; + + /* Terminate the message */ + message[++len] = '\0'; + Curl_bufref_set(out, message, len, NULL); + } + else + /* junk input => zero length output */ + Curl_bufref_set(out, "", 0, NULL); + + return CURLE_OK; +} + +/*********************************************************************** + * + * pop3_state() + * + * This is the ONLY way to change POP3 state! + */ +static void pop3_state(struct Curl_easy *data, pop3state newstate) +{ + struct pop3_conn *pop3c = &data->conn->proto.pop3c; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[] = { + "STOP", + "SERVERGREET", + "CAPA", + "STARTTLS", + "UPGRADETLS", + "AUTH", + "APOP", + "USER", + "PASS", + "COMMAND", + "QUIT", + /* LAST */ + }; + + if(pop3c->state != newstate) + infof(data, "POP3 %p state change from %s to %s", + (void *)pop3c, names[pop3c->state], names[newstate]); +#endif + + pop3c->state = newstate; +} + +/*********************************************************************** + * + * pop3_perform_capa() + * + * Sends the CAPA command in order to obtain a list of server side supported + * capabilities. + */ +static CURLcode pop3_perform_capa(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &conn->proto.pop3c; + + pop3c->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */ + pop3c->sasl.authused = SASL_AUTH_NONE; /* Clear the auth. mechanism used */ + pop3c->tls_supported = FALSE; /* Clear the TLS capability */ + + /* Send the CAPA command */ + result = Curl_pp_sendf(data, &pop3c->pp, "%s", "CAPA"); + + if(!result) + pop3_state(data, POP3_CAPA); + + return result; +} + +/*********************************************************************** + * + * pop3_perform_starttls() + * + * Sends the STLS command to start the upgrade to TLS. + */ +static CURLcode pop3_perform_starttls(struct Curl_easy *data, + struct connectdata *conn) +{ + /* Send the STLS command */ + CURLcode result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", "STLS"); + + if(!result) + pop3_state(data, POP3_STARTTLS); + + return result; +} + +/*********************************************************************** + * + * pop3_perform_upgrade_tls() + * + * Performs the upgrade to TLS. + */ +static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data, + struct connectdata *conn) +{ + /* Start the SSL connection */ + struct pop3_conn *pop3c = &conn->proto.pop3c; + CURLcode result; + bool ssldone = FALSE; + + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + if(result) + goto out; + } + + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + + if(!result) { + pop3c->ssldone = ssldone; + if(pop3c->state != POP3_UPGRADETLS) + pop3_state(data, POP3_UPGRADETLS); + + if(pop3c->ssldone) { + pop3_to_pop3s(conn); + result = pop3_perform_capa(data, conn); + } + } +out: + return result; +} + +/*********************************************************************** + * + * pop3_perform_user() + * + * Sends a clear text USER command to authenticate with. + */ +static CURLcode pop3_perform_user(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + /* Check we have a username and password to authenticate with and end the + connect phase if we don't */ + if(!data->state.aptr.user) { + pop3_state(data, POP3_STOP); + + return result; + } + + /* Send the USER command */ + result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "USER %s", + conn->user ? conn->user : ""); + if(!result) + pop3_state(data, POP3_USER); + + return result; +} + +#ifndef CURL_DISABLE_DIGEST_AUTH +/*********************************************************************** + * + * pop3_perform_apop() + * + * Sends an APOP command to authenticate with. + */ +static CURLcode pop3_perform_apop(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &conn->proto.pop3c; + size_t i; + struct MD5_context *ctxt; + unsigned char digest[MD5_DIGEST_LEN]; + char secret[2 * MD5_DIGEST_LEN + 1]; + + /* Check we have a username and password to authenticate with and end the + connect phase if we don't */ + if(!data->state.aptr.user) { + pop3_state(data, POP3_STOP); + + return result; + } + + /* Create the digest */ + ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + if(!ctxt) + return CURLE_OUT_OF_MEMORY; + + Curl_MD5_update(ctxt, (const unsigned char *) pop3c->apoptimestamp, + curlx_uztoui(strlen(pop3c->apoptimestamp))); + + Curl_MD5_update(ctxt, (const unsigned char *) conn->passwd, + curlx_uztoui(strlen(conn->passwd))); + + /* Finalise the digest */ + Curl_MD5_final(ctxt, digest); + + /* Convert the calculated 16 octet digest into a 32 byte hex string */ + for(i = 0; i < MD5_DIGEST_LEN; i++) + msnprintf(&secret[2 * i], 3, "%02x", digest[i]); + + result = Curl_pp_sendf(data, &pop3c->pp, "APOP %s %s", conn->user, secret); + + if(!result) + pop3_state(data, POP3_APOP); + + return result; +} +#endif + +/*********************************************************************** + * + * pop3_perform_auth() + * + * Sends an AUTH command allowing the client to login with the given SASL + * authentication mechanism. + */ +static CURLcode pop3_perform_auth(struct Curl_easy *data, + const char *mech, + const struct bufref *initresp) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &data->conn->proto.pop3c; + const char *ir = (const char *) Curl_bufref_ptr(initresp); + + if(ir) { /* AUTH <mech> ...<crlf> */ + /* Send the AUTH command with the initial response */ + result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s %s", mech, ir); + } + else { + /* Send the AUTH command */ + result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s", mech); + } + + return result; +} + +/*********************************************************************** + * + * pop3_continue_auth() + * + * Sends SASL continuation data. + */ +static CURLcode pop3_continue_auth(struct Curl_easy *data, + const char *mech, + const struct bufref *resp) +{ + struct pop3_conn *pop3c = &data->conn->proto.pop3c; + + (void)mech; + + return Curl_pp_sendf(data, &pop3c->pp, + "%s", (const char *) Curl_bufref_ptr(resp)); +} + +/*********************************************************************** + * + * pop3_cancel_auth() + * + * Sends SASL cancellation. + */ +static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech) +{ + struct pop3_conn *pop3c = &data->conn->proto.pop3c; + + (void)mech; + + return Curl_pp_sendf(data, &pop3c->pp, "*"); +} + +/*********************************************************************** + * + * pop3_perform_authentication() + * + * Initiates the authentication sequence, with the appropriate SASL + * authentication mechanism, falling back to APOP and clear text should a + * common mechanism not be available between the client and server. + */ +static CURLcode pop3_perform_authentication(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &conn->proto.pop3c; + saslprogress progress = SASL_IDLE; + + /* Check we have enough data to authenticate with and end the + connect phase if we don't */ + if(!Curl_sasl_can_authenticate(&pop3c->sasl, data)) { + pop3_state(data, POP3_STOP); + return result; + } + + if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_SASL) { + /* Calculate the SASL login details */ + result = Curl_sasl_start(&pop3c->sasl, data, FALSE, &progress); + + if(!result) + if(progress == SASL_INPROGRESS) + pop3_state(data, POP3_AUTH); + } + + if(!result && progress == SASL_IDLE) { +#ifndef CURL_DISABLE_DIGEST_AUTH + if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_APOP) + /* Perform APOP authentication */ + result = pop3_perform_apop(data, conn); + else +#endif + if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_CLEARTEXT) + /* Perform clear text authentication */ + result = pop3_perform_user(data, conn); + else { + /* Other mechanisms not supported */ + infof(data, "No known authentication mechanisms supported"); + result = CURLE_LOGIN_DENIED; + } + } + + return result; +} + +/*********************************************************************** + * + * pop3_perform_command() + * + * Sends a POP3 based command. + */ +static CURLcode pop3_perform_command(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct POP3 *pop3 = data->req.p.pop3; + const char *command = NULL; + + /* Calculate the default command */ + if(pop3->id[0] == '\0' || data->set.list_only) { + command = "LIST"; + + if(pop3->id[0] != '\0') + /* Message specific LIST so skip the BODY transfer */ + pop3->transfer = PPTRANSFER_INFO; + } + else + command = "RETR"; + + /* Send the command */ + if(pop3->id[0] != '\0') + result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s %s", + (pop3->custom && pop3->custom[0] != '\0' ? + pop3->custom : command), pop3->id); + else + result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", + (pop3->custom && pop3->custom[0] != '\0' ? + pop3->custom : command)); + + if(!result) + pop3_state(data, POP3_COMMAND); + + return result; +} + +/*********************************************************************** + * + * pop3_perform_quit() + * + * Performs the quit action prior to sclose() be called. + */ +static CURLcode pop3_perform_quit(struct Curl_easy *data, + struct connectdata *conn) +{ + /* Send the QUIT command */ + CURLcode result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", "QUIT"); + + if(!result) + pop3_state(data, POP3_QUIT); + + return result; +} + +/* For the initial server greeting */ +static CURLcode pop3_state_servergreet_resp(struct Curl_easy *data, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct pop3_conn *pop3c = &conn->proto.pop3c; + const char *line = data->state.buffer; + size_t len = strlen(line); + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Got unexpected pop3-server response"); + result = CURLE_WEIRD_SERVER_REPLY; + } + else { + /* Does the server support APOP authentication? */ + if(len >= 4 && line[len - 2] == '>') { + /* Look for the APOP timestamp */ + size_t i; + for(i = 3; i < len - 2; ++i) { + if(line[i] == '<') { + /* Calculate the length of the timestamp */ + size_t timestamplen = len - 1 - i; + char *at; + if(!timestamplen) + break; + + /* Allocate some memory for the timestamp */ + pop3c->apoptimestamp = (char *)calloc(1, timestamplen + 1); + + if(!pop3c->apoptimestamp) + break; + + /* Copy the timestamp */ + memcpy(pop3c->apoptimestamp, line + i, timestamplen); + pop3c->apoptimestamp[timestamplen] = '\0'; + + /* If the timestamp does not contain '@' it is not (as required by + RFC-1939) conformant to the RFC-822 message id syntax, and we + therefore do not use APOP authentication. */ + at = strchr(pop3c->apoptimestamp, '@'); + if(!at) + Curl_safefree(pop3c->apoptimestamp); + else + /* Store the APOP capability */ + pop3c->authtypes |= POP3_TYPE_APOP; + break; + } + } + } + + result = pop3_perform_capa(data, conn); + } + + return result; +} + +/* For CAPA responses */ +static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct pop3_conn *pop3c = &conn->proto.pop3c; + const char *line = data->state.buffer; + size_t len = strlen(line); + + (void)instate; /* no use for this yet */ + + /* Do we have a untagged continuation response? */ + if(pop3code == '*') { + /* Does the server support the STLS capability? */ + if(len >= 4 && !memcmp(line, "STLS", 4)) + pop3c->tls_supported = TRUE; + + /* Does the server support clear text authentication? */ + else if(len >= 4 && !memcmp(line, "USER", 4)) + pop3c->authtypes |= POP3_TYPE_CLEARTEXT; + + /* Does the server support SASL based authentication? */ + else if(len >= 5 && !memcmp(line, "SASL ", 5)) { + pop3c->authtypes |= POP3_TYPE_SASL; + + /* Advance past the SASL keyword */ + line += 5; + len -= 5; + + /* Loop through the data line */ + for(;;) { + size_t llen; + size_t wordlen; + unsigned short mechbit; + + while(len && + (*line == ' ' || *line == '\t' || + *line == '\r' || *line == '\n')) { + + line++; + len--; + } + + if(!len) + break; + + /* Extract the word */ + for(wordlen = 0; wordlen < len && line[wordlen] != ' ' && + line[wordlen] != '\t' && line[wordlen] != '\r' && + line[wordlen] != '\n';) + wordlen++; + + /* Test the word for a matching authentication mechanism */ + mechbit = Curl_sasl_decode_mech(line, wordlen, &llen); + if(mechbit && llen == wordlen) + pop3c->sasl.authmechs |= mechbit; + + line += wordlen; + len -= wordlen; + } + } + } + else { + /* Clear text is supported when CAPA isn't recognised */ + if(pop3code != '+') + pop3c->authtypes |= POP3_TYPE_CLEARTEXT; + + if(!data->set.use_ssl || Curl_conn_is_ssl(conn, FIRSTSOCKET)) + result = pop3_perform_authentication(data, conn); + else if(pop3code == '+' && pop3c->tls_supported) + /* Switch to TLS connection now */ + result = pop3_perform_starttls(data, conn); + else if(data->set.use_ssl <= CURLUSESSL_TRY) + /* Fallback and carry on with authentication */ + result = pop3_perform_authentication(data, conn); + else { + failf(data, "STLS not supported."); + result = CURLE_USE_SSL_FAILED; + } + } + + return result; +} + +/* For STARTTLS responses */ +static CURLcode pop3_state_starttls_resp(struct Curl_easy *data, + struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + (void)instate; /* no use for this yet */ + + /* Pipelining in response is forbidden. */ + if(data->conn->proto.pop3c.pp.cache_size) + return CURLE_WEIRD_SERVER_REPLY; + + if(pop3code != '+') { + if(data->set.use_ssl != CURLUSESSL_TRY) { + failf(data, "STARTTLS denied"); + result = CURLE_USE_SSL_FAILED; + } + else + result = pop3_perform_authentication(data, conn); + } + else + result = pop3_perform_upgrade_tls(data, conn); + + return result; +} + +/* For SASL authentication responses */ +static CURLcode pop3_state_auth_resp(struct Curl_easy *data, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct pop3_conn *pop3c = &conn->proto.pop3c; + saslprogress progress; + + (void)instate; /* no use for this yet */ + + result = Curl_sasl_continue(&pop3c->sasl, data, pop3code, &progress); + if(!result) + switch(progress) { + case SASL_DONE: + pop3_state(data, POP3_STOP); /* Authenticated */ + break; + case SASL_IDLE: /* No mechanism left after cancellation */ +#ifndef CURL_DISABLE_DIGEST_AUTH + if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_APOP) + /* Perform APOP authentication */ + result = pop3_perform_apop(data, conn); + else +#endif + if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_CLEARTEXT) + /* Perform clear text authentication */ + result = pop3_perform_user(data, conn); + else { + failf(data, "Authentication cancelled"); + result = CURLE_LOGIN_DENIED; + } + break; + default: + break; + } + + return result; +} + +#ifndef CURL_DISABLE_DIGEST_AUTH +/* For APOP responses */ +static CURLcode pop3_state_apop_resp(struct Curl_easy *data, int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Authentication failed: %d", pop3code); + result = CURLE_LOGIN_DENIED; + } + else + /* End of connect phase */ + pop3_state(data, POP3_STOP); + + return result; +} +#endif + +/* For USER responses */ +static CURLcode pop3_state_user_resp(struct Curl_easy *data, int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Access denied. %c", pop3code); + result = CURLE_LOGIN_DENIED; + } + else + /* Send the PASS command */ + result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "PASS %s", + conn->passwd ? conn->passwd : ""); + if(!result) + pop3_state(data, POP3_PASS); + + return result; +} + +/* For PASS responses */ +static CURLcode pop3_state_pass_resp(struct Curl_easy *data, int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Access denied. %c", pop3code); + result = CURLE_LOGIN_DENIED; + } + else + /* End of connect phase */ + pop3_state(data, POP3_STOP); + + return result; +} + +/* For command responses */ +static CURLcode pop3_state_command_resp(struct Curl_easy *data, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct POP3 *pop3 = data->req.p.pop3; + struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pingpong *pp = &pop3c->pp; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + pop3_state(data, POP3_STOP); + return CURLE_WEIRD_SERVER_REPLY; + } + + /* This 'OK' line ends with a CR LF pair which is the two first bytes of the + EOB string so count this is two matching bytes. This is necessary to make + the code detect the EOB if the only data than comes now is %2e CR LF like + when there is no body to return. */ + pop3c->eob = 2; + + /* But since this initial CR LF pair is not part of the actual body, we set + the strip counter here so that these bytes won't be delivered. */ + pop3c->strip = 2; + + if(pop3->transfer == PPTRANSFER_BODY) { + /* POP3 download */ + Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + + if(pp->cache) { + /* The header "cache" contains a bunch of data that is actually body + content so send it as such. Note that there may even be additional + "headers" after the body */ + + if(!data->req.no_body) { + result = Curl_pop3_write(data, pp->cache, pp->cache_size); + if(result) + return result; + } + + /* Free the cache */ + Curl_safefree(pp->cache); + + /* Reset the cache size */ + pp->cache_size = 0; + } + } + + /* End of DO phase */ + pop3_state(data, POP3_STOP); + + return result; +} + +static CURLcode pop3_statemachine(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int pop3code; + struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pingpong *pp = &pop3c->pp; + size_t nread = 0; + (void)data; + + /* Busy upgrading the connection; right now all I/O is SSL/TLS, not POP3 */ + if(pop3c->state == POP3_UPGRADETLS) + return pop3_perform_upgrade_tls(data, conn); + + /* Flush any data that needs to be sent */ + if(pp->sendleft) + return Curl_pp_flushsend(data, pp); + + do { + /* Read the response from the server */ + result = Curl_pp_readresp(data, sock, pp, &pop3code, &nread); + if(result) + return result; + + if(!pop3code) + break; + + /* We have now received a full POP3 server response */ + switch(pop3c->state) { + case POP3_SERVERGREET: + result = pop3_state_servergreet_resp(data, pop3code, pop3c->state); + break; + + case POP3_CAPA: + result = pop3_state_capa_resp(data, pop3code, pop3c->state); + break; + + case POP3_STARTTLS: + result = pop3_state_starttls_resp(data, conn, pop3code, pop3c->state); + break; + + case POP3_AUTH: + result = pop3_state_auth_resp(data, pop3code, pop3c->state); + break; + +#ifndef CURL_DISABLE_DIGEST_AUTH + case POP3_APOP: + result = pop3_state_apop_resp(data, pop3code, pop3c->state); + break; +#endif + + case POP3_USER: + result = pop3_state_user_resp(data, pop3code, pop3c->state); + break; + + case POP3_PASS: + result = pop3_state_pass_resp(data, pop3code, pop3c->state); + break; + + case POP3_COMMAND: + result = pop3_state_command_resp(data, pop3code, pop3c->state); + break; + + case POP3_QUIT: + pop3_state(data, POP3_STOP); + break; + + default: + /* internal error */ + pop3_state(data, POP3_STOP); + break; + } + } while(!result && pop3c->state != POP3_STOP && Curl_pp_moredata(pp)); + + return result; +} + +/* Called repeatedly until done from multi.c */ +static CURLcode pop3_multi_statemach(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct pop3_conn *pop3c = &conn->proto.pop3c; + + if((conn->handler->flags & PROTOPT_SSL) && !pop3c->ssldone) { + bool ssldone = FALSE; + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + pop3c->ssldone = ssldone; + if(result || !pop3c->ssldone) + return result; + } + + result = Curl_pp_statemach(data, &pop3c->pp, FALSE, FALSE); + *done = (pop3c->state == POP3_STOP) ? TRUE : FALSE; + + return result; +} + +static CURLcode pop3_block_statemach(struct Curl_easy *data, + struct connectdata *conn, + bool disconnecting) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &conn->proto.pop3c; + + while(pop3c->state != POP3_STOP && !result) + result = Curl_pp_statemach(data, &pop3c->pp, TRUE, disconnecting); + + return result; +} + +/* Allocate and initialize the POP3 struct for the current Curl_easy if + required */ +static CURLcode pop3_init(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct POP3 *pop3; + + pop3 = data->req.p.pop3 = calloc(1, sizeof(struct POP3)); + if(!pop3) + result = CURLE_OUT_OF_MEMORY; + + return result; +} + +/* For the POP3 "protocol connect" and "doing" phases only */ +static int pop3_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) +{ + return Curl_pp_getsock(data, &conn->proto.pop3c.pp, socks); +} + +/*********************************************************************** + * + * pop3_connect() + * + * This function should do everything that is to be considered a part of the + * connection phase. + * + * The variable 'done' points to will be TRUE if the protocol-layer connect + * phase is done when this function returns, or FALSE if not. + */ +static CURLcode pop3_connect(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pingpong *pp = &pop3c->pp; + + *done = FALSE; /* default to not done yet */ + + /* We always support persistent connections in POP3 */ + connkeep(conn, "POP3 default"); + + PINGPONG_SETUP(pp, pop3_statemachine, pop3_endofresp); + + /* Set the default preferred authentication type and mechanism */ + pop3c->preftype = POP3_TYPE_ANY; + Curl_sasl_init(&pop3c->sasl, data, &saslpop3); + + /* Initialise the pingpong layer */ + Curl_pp_setup(pp); + Curl_pp_init(data, pp); + + /* Parse the URL options */ + result = pop3_parse_url_options(conn); + if(result) + return result; + + /* Start off waiting for the server greeting response */ + pop3_state(data, POP3_SERVERGREET); + + result = pop3_multi_statemach(data, done); + + return result; +} + +/*********************************************************************** + * + * pop3_done() + * + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. + */ +static CURLcode pop3_done(struct Curl_easy *data, CURLcode status, + bool premature) +{ + CURLcode result = CURLE_OK; + struct POP3 *pop3 = data->req.p.pop3; + + (void)premature; + + if(!pop3) + return CURLE_OK; + + if(status) { + connclose(data->conn, "POP3 done with bad status"); + result = status; /* use the already set error code */ + } + + /* Cleanup our per-request based variables */ + Curl_safefree(pop3->id); + Curl_safefree(pop3->custom); + + /* Clear the transfer mode for the next request */ + pop3->transfer = PPTRANSFER_BODY; + + return result; +} + +/*********************************************************************** + * + * pop3_perform() + * + * This is the actual DO function for POP3. Get a message/listing according to + * the options previously setup. + */ +static CURLcode pop3_perform(struct Curl_easy *data, bool *connected, + bool *dophase_done) +{ + /* This is POP3 and no proxy */ + CURLcode result = CURLE_OK; + struct POP3 *pop3 = data->req.p.pop3; + + DEBUGF(infof(data, "DO phase starts")); + + if(data->req.no_body) { + /* Requested no body means no transfer */ + pop3->transfer = PPTRANSFER_INFO; + } + + *dophase_done = FALSE; /* not done yet */ + + /* Start the first command in the DO phase */ + result = pop3_perform_command(data); + if(result) + return result; + + /* Run the state-machine */ + result = pop3_multi_statemach(data, dophase_done); + *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); + + if(*dophase_done) + DEBUGF(infof(data, "DO phase is complete")); + + return result; +} + +/*********************************************************************** + * + * pop3_do() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (pop3_perform). + * + * The input argument is already checked for validity. + */ +static CURLcode pop3_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + *done = FALSE; /* default to false */ + + /* Parse the URL path */ + result = pop3_parse_url_path(data); + if(result) + return result; + + /* Parse the custom request */ + result = pop3_parse_custom_request(data); + if(result) + return result; + + result = pop3_regular_transfer(data, done); + + return result; +} + +/*********************************************************************** + * + * pop3_disconnect() + * + * Disconnect from an POP3 server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +static CURLcode pop3_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection) +{ + struct pop3_conn *pop3c = &conn->proto.pop3c; + (void)data; + + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. */ + + if(!dead_connection && conn->bits.protoconnstart) { + if(!pop3_perform_quit(data, conn)) + (void)pop3_block_statemach(data, conn, TRUE); /* ignore errors on QUIT */ + } + + /* Disconnect from the server */ + Curl_pp_disconnect(&pop3c->pp); + + /* Cleanup the SASL module */ + Curl_sasl_cleanup(conn, pop3c->sasl.authused); + + /* Cleanup our connection based variables */ + Curl_safefree(pop3c->apoptimestamp); + + return CURLE_OK; +} + +/* Call this when the DO phase has completed */ +static CURLcode pop3_dophase_done(struct Curl_easy *data, bool connected) +{ + (void)data; + (void)connected; + + return CURLE_OK; +} + +/* Called from multi.c while DOing */ +static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done) +{ + CURLcode result = pop3_multi_statemach(data, dophase_done); + + if(result) + DEBUGF(infof(data, "DO phase failed")); + else if(*dophase_done) { + result = pop3_dophase_done(data, FALSE /* not connected */); + + DEBUGF(infof(data, "DO phase is complete")); + } + + return result; +} + +/*********************************************************************** + * + * pop3_regular_transfer() + * + * The input argument is already checked for validity. + * + * Performs all commands done before a regular transfer between a local and a + * remote host. + */ +static CURLcode pop3_regular_transfer(struct Curl_easy *data, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + bool connected = FALSE; + + /* Make sure size is unknown at this point */ + data->req.size = -1; + + /* Set the progress data */ + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, -1); + Curl_pgrsSetDownloadSize(data, -1); + + /* Carry out the perform */ + result = pop3_perform(data, &connected, dophase_done); + + /* Perform post DO phase operations if necessary */ + if(!result && *dophase_done) + result = pop3_dophase_done(data, connected); + + return result; +} + +static CURLcode pop3_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + /* Initialise the POP3 layer */ + CURLcode result = pop3_init(data); + if(result) + return result; + + /* Clear the TLS upgraded flag */ + conn->bits.tls_upgraded = FALSE; + + return CURLE_OK; +} + +/*********************************************************************** + * + * pop3_parse_url_options() + * + * Parse the URL login options. + */ +static CURLcode pop3_parse_url_options(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &conn->proto.pop3c; + const char *ptr = conn->options; + + while(!result && ptr && *ptr) { + const char *key = ptr; + const char *value; + + while(*ptr && *ptr != '=') + ptr++; + + value = ptr + 1; + + while(*ptr && *ptr != ';') + ptr++; + + if(strncasecompare(key, "AUTH=", 5)) { + result = Curl_sasl_parse_url_auth_option(&pop3c->sasl, + value, ptr - value); + + if(result && strncasecompare(value, "+APOP", ptr - value)) { + pop3c->preftype = POP3_TYPE_APOP; + pop3c->sasl.prefmech = SASL_AUTH_NONE; + result = CURLE_OK; + } + } + else + result = CURLE_URL_MALFORMAT; + + if(*ptr == ';') + ptr++; + } + + if(pop3c->preftype != POP3_TYPE_APOP) + switch(pop3c->sasl.prefmech) { + case SASL_AUTH_NONE: + pop3c->preftype = POP3_TYPE_NONE; + break; + case SASL_AUTH_DEFAULT: + pop3c->preftype = POP3_TYPE_ANY; + break; + default: + pop3c->preftype = POP3_TYPE_SASL; + break; + } + + return result; +} + +/*********************************************************************** + * + * pop3_parse_url_path() + * + * Parse the URL path into separate path components. + */ +static CURLcode pop3_parse_url_path(struct Curl_easy *data) +{ + /* The POP3 struct is already initialised in pop3_connect() */ + struct POP3 *pop3 = data->req.p.pop3; + const char *path = &data->state.up.path[1]; /* skip leading path */ + + /* URL decode the path for the message ID */ + return Curl_urldecode(path, 0, &pop3->id, NULL, REJECT_CTRL); +} + +/*********************************************************************** + * + * pop3_parse_custom_request() + * + * Parse the custom request. + */ +static CURLcode pop3_parse_custom_request(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct POP3 *pop3 = data->req.p.pop3; + const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + + /* URL decode the custom request */ + if(custom) + result = Curl_urldecode(custom, 0, &pop3->custom, NULL, REJECT_CTRL); + + return result; +} + +/*********************************************************************** + * + * Curl_pop3_write() + * + * This function scans the body after the end-of-body and writes everything + * until the end is found. + */ +CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) +{ + /* This code could be made into a special function in the handler struct */ + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + struct connectdata *conn = data->conn; + struct pop3_conn *pop3c = &conn->proto.pop3c; + bool strip_dot = FALSE; + size_t last = 0; + size_t i; + + /* Search through the buffer looking for the end-of-body marker which is + 5 bytes (0d 0a 2e 0d 0a). Note that a line starting with a dot matches + the eob so the server will have prefixed it with an extra dot which we + need to strip out. Additionally the marker could of course be spread out + over 5 different data chunks. */ + for(i = 0; i < nread; i++) { + size_t prev = pop3c->eob; + + switch(str[i]) { + case 0x0d: + if(pop3c->eob == 0) { + pop3c->eob++; + + if(i) { + /* Write out the body part that didn't match */ + result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last], + i - last); + + if(result) + return result; + + last = i; + } + } + else if(pop3c->eob == 3) + pop3c->eob++; + else + /* If the character match wasn't at position 0 or 3 then restart the + pattern matching */ + pop3c->eob = 1; + break; + + case 0x0a: + if(pop3c->eob == 1 || pop3c->eob == 4) + pop3c->eob++; + else + /* If the character match wasn't at position 1 or 4 then start the + search again */ + pop3c->eob = 0; + break; + + case 0x2e: + if(pop3c->eob == 2) + pop3c->eob++; + else if(pop3c->eob == 3) { + /* We have an extra dot after the CRLF which we need to strip off */ + strip_dot = TRUE; + pop3c->eob = 0; + } + else + /* If the character match wasn't at position 2 then start the search + again */ + pop3c->eob = 0; + break; + + default: + pop3c->eob = 0; + break; + } + + /* Did we have a partial match which has subsequently failed? */ + if(prev && prev >= pop3c->eob) { + /* Strip can only be non-zero for the very first mismatch after CRLF + and then both prev and strip are equal and nothing will be output + below */ + while(prev && pop3c->strip) { + prev--; + pop3c->strip--; + } + + if(prev) { + /* If the partial match was the CRLF and dot then only write the CRLF + as the server would have inserted the dot */ + if(strip_dot && prev - 1 > 0) { + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB, + prev - 1); + } + else if(!strip_dot) { + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB, + prev); + } + else { + result = CURLE_OK; + } + + if(result) + return result; + + last = i; + strip_dot = FALSE; + } + } + } + + if(pop3c->eob == POP3_EOB_LEN) { + /* We have a full match so the transfer is done, however we must transfer + the CRLF at the start of the EOB as this is considered to be part of the + message as per RFC-1939, sect. 3 */ + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB, 2); + + k->keepon &= ~KEEP_RECV; + pop3c->eob = 0; + + return result; + } + + if(pop3c->eob) + /* While EOB is matching nothing should be output */ + return CURLE_OK; + + if(nread - last) { + result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last], + nread - last); + } + + return result; +} + +#endif /* CURL_DISABLE_POP3 */ diff --git a/Utilities/cmcurl/lib/pop3.h b/Utilities/cmcurl/lib/pop3.h new file mode 100644 index 0000000..83f0f83 --- /dev/null +++ b/Utilities/cmcurl/lib/pop3.h @@ -0,0 +1,97 @@ +#ifndef HEADER_CURL_POP3_H +#define HEADER_CURL_POP3_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "pingpong.h" +#include "curl_sasl.h" + +/**************************************************************************** + * POP3 unique setup + ***************************************************************************/ +typedef enum { + POP3_STOP, /* do nothing state, stops the state machine */ + POP3_SERVERGREET, /* waiting for the initial greeting immediately after + a connect */ + POP3_CAPA, + POP3_STARTTLS, + POP3_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS + (multi mode only) */ + POP3_AUTH, + POP3_APOP, + POP3_USER, + POP3_PASS, + POP3_COMMAND, + POP3_QUIT, + POP3_LAST /* never used */ +} pop3state; + +/* This POP3 struct is used in the Curl_easy. All POP3 data that is + connection-oriented must be in pop3_conn to properly deal with the fact that + perhaps the Curl_easy is changed between the times the connection is + used. */ +struct POP3 { + curl_pp_transfer transfer; + char *id; /* Message ID */ + char *custom; /* Custom Request */ +}; + +/* pop3_conn is used for struct connection-oriented data in the connectdata + struct */ +struct pop3_conn { + struct pingpong pp; + pop3state state; /* Always use pop3.c:state() to change state! */ + size_t eob; /* Number of bytes of the EOB (End Of Body) that + have been received so far */ + size_t strip; /* Number of bytes from the start to ignore as + non-body */ + struct SASL sasl; /* SASL-related storage */ + char *apoptimestamp; /* APOP timestamp from the server greeting */ + unsigned char authtypes; /* Accepted authentication types */ + unsigned char preftype; /* Preferred authentication type */ + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(tls_supported); /* StartTLS capability supported by server */ +}; + +extern const struct Curl_handler Curl_handler_pop3; +extern const struct Curl_handler Curl_handler_pop3s; + +/* Authentication type flags */ +#define POP3_TYPE_CLEARTEXT (1 << 0) +#define POP3_TYPE_APOP (1 << 1) +#define POP3_TYPE_SASL (1 << 2) + +/* Authentication type values */ +#define POP3_TYPE_NONE 0 +#define POP3_TYPE_ANY (POP3_TYPE_CLEARTEXT|POP3_TYPE_APOP|POP3_TYPE_SASL) + +/* This is the 5-bytes End-Of-Body marker for POP3 */ +#define POP3_EOB "\x0d\x0a\x2e\x0d\x0a" +#define POP3_EOB_LEN 5 + +/* This function scans the body after the end-of-body and writes everything + * until the end is found */ +CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread); + +#endif /* HEADER_CURL_POP3_H */ diff --git a/Utilities/cmcurl/lib/progress.c b/Utilities/cmcurl/lib/progress.c new file mode 100644 index 0000000..e96cbf7 --- /dev/null +++ b/Utilities/cmcurl/lib/progress.c @@ -0,0 +1,625 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include "sendf.h" +#include "multiif.h" +#include "progress.h" +#include "timeval.h" +#include "curl_printf.h" + +/* check rate limits within this many recent milliseconds, at minimum. */ +#define MIN_RATE_LIMIT_PERIOD 3000 + +#ifndef CURL_DISABLE_PROGRESS_METER +/* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero + byte) */ +static void time2str(char *r, curl_off_t seconds) +{ + curl_off_t h; + if(seconds <= 0) { + strcpy(r, "--:--:--"); + return; + } + h = seconds / CURL_OFF_T_C(3600); + if(h <= CURL_OFF_T_C(99)) { + curl_off_t m = (seconds - (h*CURL_OFF_T_C(3600))) / CURL_OFF_T_C(60); + curl_off_t s = (seconds - (h*CURL_OFF_T_C(3600))) - (m*CURL_OFF_T_C(60)); + msnprintf(r, 9, "%2" CURL_FORMAT_CURL_OFF_T ":%02" CURL_FORMAT_CURL_OFF_T + ":%02" CURL_FORMAT_CURL_OFF_T, h, m, s); + } + else { + /* this equals to more than 99 hours, switch to a more suitable output + format to fit within the limits. */ + curl_off_t d = seconds / CURL_OFF_T_C(86400); + h = (seconds - (d*CURL_OFF_T_C(86400))) / CURL_OFF_T_C(3600); + if(d <= CURL_OFF_T_C(999)) + msnprintf(r, 9, "%3" CURL_FORMAT_CURL_OFF_T + "d %02" CURL_FORMAT_CURL_OFF_T "h", d, h); + else + msnprintf(r, 9, "%7" CURL_FORMAT_CURL_OFF_T "d", d); + } +} + +/* The point of this function would be to return a string of the input data, + but never longer than 5 columns (+ one zero byte). + Add suffix k, M, G when suitable... */ +static char *max5data(curl_off_t bytes, char *max5) +{ +#define ONE_KILOBYTE CURL_OFF_T_C(1024) +#define ONE_MEGABYTE (CURL_OFF_T_C(1024) * ONE_KILOBYTE) +#define ONE_GIGABYTE (CURL_OFF_T_C(1024) * ONE_MEGABYTE) +#define ONE_TERABYTE (CURL_OFF_T_C(1024) * ONE_GIGABYTE) +#define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE) + + if(bytes < CURL_OFF_T_C(100000)) + msnprintf(max5, 6, "%5" CURL_FORMAT_CURL_OFF_T, bytes); + + else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE) + msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "k", bytes/ONE_KILOBYTE); + + else if(bytes < CURL_OFF_T_C(100) * ONE_MEGABYTE) + /* 'XX.XM' is good as long as we're less than 100 megs */ + msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" + CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE, + (bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) ); + + else if(bytes < CURL_OFF_T_C(10000) * ONE_MEGABYTE) + /* 'XXXXM' is good until we're at 10000MB or above */ + msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE); + + else if(bytes < CURL_OFF_T_C(100) * ONE_GIGABYTE) + /* 10000 MB - 100 GB, we show it as XX.XG */ + msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" + CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE, + (bytes%ONE_GIGABYTE) / (ONE_GIGABYTE/CURL_OFF_T_C(10)) ); + + else if(bytes < CURL_OFF_T_C(10000) * ONE_GIGABYTE) + /* up to 10000GB, display without decimal: XXXXG */ + msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE); + + else if(bytes < CURL_OFF_T_C(10000) * ONE_TERABYTE) + /* up to 10000TB, display without decimal: XXXXT */ + msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "T", bytes/ONE_TERABYTE); + + else + /* up to 10000PB, display without decimal: XXXXP */ + msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "P", bytes/ONE_PETABYTE); + + /* 16384 petabytes (16 exabytes) is the maximum a 64 bit unsigned number can + hold, but our data type is signed so 8192PB will be the maximum. */ + + return max5; +} +#endif + +/* + + New proposed interface, 9th of February 2000: + + pgrsStartNow() - sets start time + pgrsSetDownloadSize(x) - known expected download size + pgrsSetUploadSize(x) - known expected upload size + pgrsSetDownloadCounter() - amount of data currently downloaded + 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; + rc = Curl_pgrsUpdate(data); /* the final (forced) update */ + if(rc) + return rc; + + if(!(data->progress.flags & PGRS_HIDE) && + !data->progress.callback) + /* only output if we don't use a progress callback and we're not + * hidden */ + fprintf(data->set.err, "\n"); + + data->progress.speeder_c = 0; /* reset the progress meter display */ + return 0; +} + +/* reset the known transfer sizes */ +void Curl_pgrsResetTransferSizes(struct Curl_easy *data) +{ + Curl_pgrsSetDownloadSize(data, -1); + Curl_pgrsSetUploadSize(data, -1); +} + +/* + * + * Curl_pgrsTimeWas(). Store the timestamp time at the given label. + */ +void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, + struct curltime timestamp) +{ + timediff_t *delta = NULL; + + switch(timer) { + default: + case TIMER_NONE: + /* mistake filter */ + break; + case TIMER_STARTOP: + /* This is set at the start of a transfer */ + data->progress.t_startop = timestamp; + break; + case TIMER_STARTSINGLE: + /* This is set at the start of each single fetch */ + data->progress.t_startsingle = timestamp; + data->progress.is_t_startransfer_set = false; + break; + case TIMER_STARTACCEPT: + data->progress.t_acceptdata = timestamp; + break; + case TIMER_NAMELOOKUP: + delta = &data->progress.t_nslookup; + break; + case TIMER_CONNECT: + delta = &data->progress.t_connect; + break; + case TIMER_APPCONNECT: + delta = &data->progress.t_appconnect; + break; + case TIMER_PRETRANSFER: + delta = &data->progress.t_pretransfer; + break; + case TIMER_STARTTRANSFER: + delta = &data->progress.t_starttransfer; + /* prevent updating t_starttransfer unless: + * 1) this is the first time we're setting t_starttransfer + * 2) a redirect has occurred since the last time t_starttransfer was set + * This prevents repeated invocations of the function from incorrectly + * changing the t_starttransfer time. + */ + if(data->progress.is_t_startransfer_set) { + return; + } + else { + data->progress.is_t_startransfer_set = true; + break; + } + case TIMER_POSTRANSFER: + /* this is the normal end-of-transfer thing */ + break; + case TIMER_REDIRECT: + data->progress.t_redirect = Curl_timediff_us(timestamp, + data->progress.start); + break; + } + if(delta) { + timediff_t us = Curl_timediff_us(timestamp, data->progress.t_startsingle); + if(us < 1) + us = 1; /* make sure at least one microsecond passed */ + *delta += us; + } +} + +/* + * + * Curl_pgrsTime(). Store the current time at the given label. This fetches a + * fresh "now" and returns it. + * + * @unittest: 1399 + */ +struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer) +{ + struct curltime now = Curl_now(); + + Curl_pgrsTimeWas(data, timer, now); + return now; +} + +void Curl_pgrsStartNow(struct Curl_easy *data) +{ + data->progress.speeder_c = 0; /* reset the progress meter display */ + data->progress.start = Curl_now(); + data->progress.is_t_startransfer_set = false; + data->progress.ul_limit_start = data->progress.start; + data->progress.dl_limit_start = data->progress.start; + data->progress.ul_limit_size = 0; + data->progress.dl_limit_size = 0; + data->progress.downloaded = 0; + data->progress.uploaded = 0; + /* clear all bits except HIDE and HEADERS_OUT */ + data->progress.flags &= PGRS_HIDE|PGRS_HEADERS_OUT; + Curl_ratelimit(data, data->progress.start); +} + +/* + * This is used to handle speed limits, calculating how many milliseconds to + * wait until we're back under the speed limit, if needed. + * + * The way it works is by having a "starting point" (time & amount of data + * transferred by then) used in the speed computation, to be used instead of + * the start of the transfer. This starting point is regularly moved as + * transfer goes on, to keep getting accurate values (instead of average over + * the entire transfer). + * + * This function takes the current amount of data transferred, the amount at + * the starting point, the limit (in bytes/s), the time of the starting point + * and the current time. + * + * Returns 0 if no waiting is needed or when no waiting is needed but the + * starting point should be reset (to current); or the number of milliseconds + * to wait to get back under the speed limit. + */ +timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, + curl_off_t startsize, + curl_off_t limit, + struct curltime start, + struct curltime now) +{ + curl_off_t size = cursize - startsize; + timediff_t minimum; + timediff_t actual; + + if(!limit || !size) + return 0; + + /* + * 'minimum' is the number of milliseconds 'size' should take to download to + * stay below 'limit'. + */ + if(size < CURL_OFF_T_MAX/1000) + minimum = (timediff_t) (CURL_OFF_T_C(1000) * size / limit); + else { + minimum = (timediff_t) (size / limit); + if(minimum < TIMEDIFF_T_MAX/1000) + minimum *= 1000; + else + minimum = TIMEDIFF_T_MAX; + } + + /* + * 'actual' is the time in milliseconds it took to actually download the + * last 'size' bytes. + */ + actual = Curl_timediff_ceil(now, start); + if(actual < minimum) { + /* if it downloaded the data faster than the limit, make it wait the + difference */ + return (minimum - actual); + } + + return 0; +} + +/* + * Set the number of downloaded bytes so far. + */ +CURLcode Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size) +{ + data->progress.downloaded = size; + return CURLE_OK; +} + +/* + * Update the timestamp and sizestamp to use for rate limit calculations. + */ +void Curl_ratelimit(struct Curl_easy *data, struct curltime now) +{ + /* don't set a new stamp unless the time since last update is long enough */ + if(data->set.max_recv_speed) { + if(Curl_timediff(now, data->progress.dl_limit_start) >= + MIN_RATE_LIMIT_PERIOD) { + data->progress.dl_limit_start = now; + data->progress.dl_limit_size = data->progress.downloaded; + } + } + if(data->set.max_send_speed) { + if(Curl_timediff(now, data->progress.ul_limit_start) >= + MIN_RATE_LIMIT_PERIOD) { + data->progress.ul_limit_start = now; + data->progress.ul_limit_size = data->progress.uploaded; + } + } +} + +/* + * Set the number of uploaded bytes so far. + */ +void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size) +{ + data->progress.uploaded = size; +} + +void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size) +{ + if(size >= 0) { + data->progress.size_dl = size; + data->progress.flags |= PGRS_DL_SIZE_KNOWN; + } + else { + data->progress.size_dl = 0; + data->progress.flags &= ~PGRS_DL_SIZE_KNOWN; + } +} + +void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size) +{ + if(size >= 0) { + data->progress.size_ul = size; + data->progress.flags |= PGRS_UL_SIZE_KNOWN; + } + else { + data->progress.size_ul = 0; + data->progress.flags &= ~PGRS_UL_SIZE_KNOWN; + } +} + +/* returns the average speed in bytes / second */ +static curl_off_t trspeed(curl_off_t size, /* number of bytes */ + curl_off_t us) /* microseconds */ +{ + if(us < 1) + return size * 1000000; + else if(size < CURL_OFF_T_MAX/1000000) + return (size * 1000000) / us; + else if(us >= 1000000) + return size / (us / 1000000); + else + return CURL_OFF_T_MAX; +} + +/* returns TRUE if it's time to show the progress meter */ +static bool progress_calc(struct Curl_easy *data, struct curltime now) +{ + bool timetoshow = FALSE; + struct Progress * const p = &data->progress; + + /* The time spent so far (from the start) in microseconds */ + p->timespent = Curl_timediff_us(now, p->start); + p->dlspeed = trspeed(p->downloaded, p->timespent); + p->ulspeed = trspeed(p->uploaded, p->timespent); + + /* Calculations done at most once a second, unless end is reached */ + if(p->lastshow != now.tv_sec) { + int countindex; /* amount of seconds stored in the speeder array */ + int nowindex = p->speeder_c% CURR_TIME; + p->lastshow = now.tv_sec; + timetoshow = TRUE; + + /* Let's do the "current speed" thing, with the dl + ul speeds + combined. Store the speed at entry 'nowindex'. */ + p->speeder[ nowindex ] = p->downloaded + p->uploaded; + + /* remember the exact time for this moment */ + p->speeder_time [ nowindex ] = now; + + /* advance our speeder_c counter, which is increased every time we get + here and we expect it to never wrap as 2^32 is a lot of seconds! */ + p->speeder_c++; + + /* figure out how many index entries of data we have stored in our speeder + array. With N_ENTRIES filled in, we have about N_ENTRIES-1 seconds of + transfer. Imagine, after one second we have filled in two entries, + after two seconds we've filled in three entries etc. */ + countindex = ((p->speeder_c >= CURR_TIME)? CURR_TIME:p->speeder_c) - 1; + + /* first of all, we don't do this if there's no counted seconds yet */ + if(countindex) { + int checkindex; + timediff_t span_ms; + curl_off_t amount; + + /* Get the index position to compare with the 'nowindex' position. + Get the oldest entry possible. While we have less than CURR_TIME + entries, the first entry will remain the oldest. */ + checkindex = (p->speeder_c >= CURR_TIME)? p->speeder_c%CURR_TIME:0; + + /* Figure out the exact time for the time span */ + span_ms = Curl_timediff(now, p->speeder_time[checkindex]); + if(0 == span_ms) + span_ms = 1; /* at least one millisecond MUST have passed */ + + /* Calculate the average speed the last 'span_ms' milliseconds */ + amount = p->speeder[nowindex]- p->speeder[checkindex]; + + if(amount > CURL_OFF_T_C(4294967) /* 0xffffffff/1000 */) + /* the 'amount' value is bigger than would fit in 32 bits if + multiplied with 1000, so we use the double math for this */ + p->current_speed = (curl_off_t) + ((double)amount/((double)span_ms/1000.0)); + else + /* the 'amount' value is small enough to fit within 32 bits even + when multiplied with 1000 */ + p->current_speed = amount*CURL_OFF_T_C(1000)/span_ms; + } + else + /* the first second we use the average */ + p->current_speed = p->ulspeed + p->dlspeed; + + } /* Calculations end */ + return timetoshow; +} + +#ifndef CURL_DISABLE_PROGRESS_METER +static void progress_meter(struct Curl_easy *data) +{ + char max5[6][10]; + curl_off_t dlpercen = 0; + curl_off_t ulpercen = 0; + curl_off_t total_percen = 0; + curl_off_t total_transfer; + curl_off_t total_expected_transfer; + char time_left[10]; + char time_total[10]; + char time_spent[10]; + curl_off_t ulestimate = 0; + curl_off_t dlestimate = 0; + curl_off_t total_estimate; + curl_off_t timespent = + (curl_off_t)data->progress.timespent/1000000; /* seconds */ + + if(!(data->progress.flags & PGRS_HEADERS_OUT)) { + if(data->state.resume_from) { + fprintf(data->set.err, + "** Resuming transfer from byte position %" + CURL_FORMAT_CURL_OFF_T "\n", data->state.resume_from); + } + fprintf(data->set.err, + " %% Total %% Received %% Xferd Average Speed " + "Time Time Time Current\n" + " Dload Upload " + "Total Spent Left Speed\n"); + data->progress.flags |= PGRS_HEADERS_OUT; /* headers are shown */ + } + + /* Figure out the estimated time of arrival for the upload */ + if((data->progress.flags & PGRS_UL_SIZE_KNOWN) && + (data->progress.ulspeed > CURL_OFF_T_C(0))) { + ulestimate = data->progress.size_ul / data->progress.ulspeed; + + if(data->progress.size_ul > CURL_OFF_T_C(10000)) + ulpercen = data->progress.uploaded / + (data->progress.size_ul/CURL_OFF_T_C(100)); + else if(data->progress.size_ul > CURL_OFF_T_C(0)) + ulpercen = (data->progress.uploaded*100) / + data->progress.size_ul; + } + + /* ... and the download */ + if((data->progress.flags & PGRS_DL_SIZE_KNOWN) && + (data->progress.dlspeed > CURL_OFF_T_C(0))) { + dlestimate = data->progress.size_dl / data->progress.dlspeed; + + if(data->progress.size_dl > CURL_OFF_T_C(10000)) + dlpercen = data->progress.downloaded / + (data->progress.size_dl/CURL_OFF_T_C(100)); + else if(data->progress.size_dl > CURL_OFF_T_C(0)) + dlpercen = (data->progress.downloaded*100) / + data->progress.size_dl; + } + + /* Now figure out which of them is slower and use that one for the + total estimate! */ + total_estimate = ulestimate>dlestimate?ulestimate:dlestimate; + + /* create the three time strings */ + time2str(time_left, total_estimate > 0?(total_estimate - timespent):0); + time2str(time_total, total_estimate); + time2str(time_spent, timespent); + + /* Get the total amount of data expected to get transferred */ + total_expected_transfer = + ((data->progress.flags & PGRS_UL_SIZE_KNOWN)? + data->progress.size_ul:data->progress.uploaded)+ + ((data->progress.flags & PGRS_DL_SIZE_KNOWN)? + data->progress.size_dl:data->progress.downloaded); + + /* We have transferred this much so far */ + total_transfer = data->progress.downloaded + data->progress.uploaded; + + /* Get the percentage of data transferred so far */ + if(total_expected_transfer > CURL_OFF_T_C(10000)) + total_percen = total_transfer / + (total_expected_transfer/CURL_OFF_T_C(100)); + else if(total_expected_transfer > CURL_OFF_T_C(0)) + total_percen = (total_transfer*100) / total_expected_transfer; + + fprintf(data->set.err, + "\r" + "%3" CURL_FORMAT_CURL_OFF_T " %s " + "%3" CURL_FORMAT_CURL_OFF_T " %s " + "%3" CURL_FORMAT_CURL_OFF_T " %s %s %s %s %s %s %s", + total_percen, /* 3 letters */ /* total % */ + max5data(total_expected_transfer, max5[2]), /* total size */ + dlpercen, /* 3 letters */ /* rcvd % */ + max5data(data->progress.downloaded, max5[0]), /* rcvd size */ + ulpercen, /* 3 letters */ /* xfer % */ + max5data(data->progress.uploaded, max5[1]), /* xfer size */ + max5data(data->progress.dlspeed, max5[3]), /* avrg dl speed */ + max5data(data->progress.ulspeed, max5[4]), /* avrg ul speed */ + time_total, /* 8 letters */ /* total time */ + time_spent, /* 8 letters */ /* time spent */ + time_left, /* 8 letters */ /* time left */ + max5data(data->progress.current_speed, max5[5]) + ); + + /* we flush the output stream to make it appear as soon as possible */ + fflush(data->set.err); +} +#else + /* progress bar disabled */ +#define progress_meter(x) Curl_nop_stmt +#endif + + +/* + * Curl_pgrsUpdate() returns 0 for success or the value returned by the + * progress callback! + */ +int Curl_pgrsUpdate(struct Curl_easy *data) +{ + struct curltime now = Curl_now(); /* what time is it */ + bool showprogress = progress_calc(data, now); + if(!(data->progress.flags & PGRS_HIDE)) { + if(data->set.fxferinfo) { + int result; + /* There's a callback set, call that */ + Curl_set_in_callback(data, true); + result = data->set.fxferinfo(data->set.progress_client, + data->progress.size_dl, + data->progress.downloaded, + data->progress.size_ul, + data->progress.uploaded); + Curl_set_in_callback(data, false); + if(result != CURL_PROGRESSFUNC_CONTINUE) { + if(result) + failf(data, "Callback aborted"); + return result; + } + } + else if(data->set.fprogress) { + int result; + /* The older deprecated callback is set, call that */ + Curl_set_in_callback(data, true); + result = data->set.fprogress(data->set.progress_client, + (double)data->progress.size_dl, + (double)data->progress.downloaded, + (double)data->progress.size_ul, + (double)data->progress.uploaded); + Curl_set_in_callback(data, false); + if(result != CURL_PROGRESSFUNC_CONTINUE) { + if(result) + failf(data, "Callback aborted"); + return result; + } + } + + if(showprogress) + progress_meter(data); + } + + return 0; +} diff --git a/Utilities/cmcurl/lib/progress.h b/Utilities/cmcurl/lib/progress.h new file mode 100644 index 0000000..fc39e34 --- /dev/null +++ b/Utilities/cmcurl/lib/progress.h @@ -0,0 +1,76 @@ +#ifndef HEADER_CURL_PROGRESS_H +#define HEADER_CURL_PROGRESS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "timeval.h" + + +typedef enum { + TIMER_NONE, + TIMER_STARTOP, + TIMER_STARTSINGLE, + TIMER_NAMELOOKUP, + TIMER_CONNECT, + TIMER_APPCONNECT, + TIMER_PRETRANSFER, + TIMER_STARTTRANSFER, + TIMER_POSTRANSFER, + TIMER_STARTACCEPT, + TIMER_REDIRECT, + TIMER_LAST /* must be last */ +} timerid; + +int Curl_pgrsDone(struct Curl_easy *data); +void Curl_pgrsStartNow(struct Curl_easy *data); +void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size); +void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size); + +/* It is fine to not check the return code if 'size' is set to 0 */ +CURLcode Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size); + +void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size); +void Curl_ratelimit(struct Curl_easy *data, struct curltime now); +int Curl_pgrsUpdate(struct Curl_easy *data); +void Curl_pgrsResetTransferSizes(struct Curl_easy *data); +struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer); +timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, + curl_off_t startsize, + curl_off_t limit, + struct curltime start, + struct curltime now); +/** + * Update progress timer with the elapsed time from its start to `timestamp`. + * This allows updating timers later and is used by happy eyeballing, where + * we only want to record the winner's times. + */ +void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, + struct curltime timestamp); + +#define PGRS_HIDE (1<<4) +#define PGRS_UL_SIZE_KNOWN (1<<5) +#define PGRS_DL_SIZE_KNOWN (1<<6) +#define PGRS_HEADERS_OUT (1<<7) /* set when the headers have been written */ + +#endif /* HEADER_CURL_PROGRESS_H */ diff --git a/Utilities/cmcurl/lib/psl.c b/Utilities/cmcurl/lib/psl.c new file mode 100644 index 0000000..626a203 --- /dev/null +++ b/Utilities/cmcurl/lib/psl.c @@ -0,0 +1,113 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#ifdef USE_LIBPSL + +#include "psl.h" +#include "share.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +void Curl_psl_destroy(struct PslCache *pslcache) +{ + if(pslcache->psl) { + if(pslcache->dynamic) + psl_free((psl_ctx_t *) pslcache->psl); + pslcache->psl = NULL; + pslcache->dynamic = FALSE; + } +} + +static time_t now_seconds(void) +{ + struct curltime now = Curl_now(); + + return now.tv_sec; +} + +const psl_ctx_t *Curl_psl_use(struct Curl_easy *easy) +{ + struct PslCache *pslcache = easy->psl; + const psl_ctx_t *psl; + time_t now; + + if(!pslcache) + return NULL; + + Curl_share_lock(easy, CURL_LOCK_DATA_PSL, CURL_LOCK_ACCESS_SHARED); + now = now_seconds(); + if(!pslcache->psl || pslcache->expires <= now) { + /* Let a chance to other threads to do the job: avoids deadlock. */ + Curl_share_unlock(easy, CURL_LOCK_DATA_PSL); + + /* Update cache: this needs an exclusive lock. */ + Curl_share_lock(easy, CURL_LOCK_DATA_PSL, CURL_LOCK_ACCESS_SINGLE); + + /* Recheck in case another thread did the job. */ + now = now_seconds(); + if(!pslcache->psl || pslcache->expires <= now) { + bool dynamic = FALSE; + time_t expires = TIME_T_MAX; + +#if defined(PSL_VERSION_NUMBER) && PSL_VERSION_NUMBER >= 0x001000 + psl = psl_latest(NULL); + dynamic = psl != NULL; + /* Take care of possible time computation overflow. */ + expires = now < TIME_T_MAX - PSL_TTL? now + PSL_TTL: TIME_T_MAX; + + /* Only get the built-in PSL if we do not already have the "latest". */ + if(!psl && !pslcache->dynamic) +#endif + + psl = psl_builtin(); + + if(psl) { + Curl_psl_destroy(pslcache); + pslcache->psl = psl; + pslcache->dynamic = dynamic; + pslcache->expires = expires; + } + } + Curl_share_unlock(easy, CURL_LOCK_DATA_PSL); /* Release exclusive lock. */ + Curl_share_lock(easy, CURL_LOCK_DATA_PSL, CURL_LOCK_ACCESS_SHARED); + } + psl = pslcache->psl; + if(!psl) + Curl_share_unlock(easy, CURL_LOCK_DATA_PSL); + return psl; +} + +void Curl_psl_release(struct Curl_easy *easy) +{ + Curl_share_unlock(easy, CURL_LOCK_DATA_PSL); +} + +#endif /* USE_LIBPSL */ diff --git a/Utilities/cmcurl/lib/psl.h b/Utilities/cmcurl/lib/psl.h new file mode 100644 index 0000000..23cfa92 --- /dev/null +++ b/Utilities/cmcurl/lib/psl.h @@ -0,0 +1,49 @@ +#ifndef HEADER_PSL_H +#define HEADER_PSL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#ifdef USE_LIBPSL +#include <libpsl.h> + +#define PSL_TTL (72 * 3600) /* PSL time to live before a refresh. */ + +struct PslCache { + const psl_ctx_t *psl; /* The PSL. */ + time_t expires; /* Time this PSL life expires. */ + bool dynamic; /* PSL should be released when no longer needed. */ +}; + +const psl_ctx_t *Curl_psl_use(struct Curl_easy *easy); +void Curl_psl_release(struct Curl_easy *easy); +void Curl_psl_destroy(struct PslCache *pslcache); + +#else + +#define Curl_psl_use(easy) NULL +#define Curl_psl_release(easy) +#define Curl_psl_destroy(pslcache) + +#endif /* USE_LIBPSL */ +#endif /* HEADER_PSL_H */ diff --git a/Utilities/cmcurl/lib/rand.c b/Utilities/cmcurl/lib/rand.c new file mode 100644 index 0000000..3383c49 --- /dev/null +++ b/Utilities/cmcurl/lib/rand.c @@ -0,0 +1,289 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <limits.h> + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#include <curl/curl.h> +#include "urldata.h" +#include "vtls/vtls.h" +#include "sendf.h" +#include "timeval.h" +#include "rand.h" +#include "escape.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef _WIN32 + +#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600 +# define HAVE_WIN_BCRYPTGENRANDOM +# include <bcrypt.h> +# ifdef _MSC_VER +# pragma comment(lib, "bcrypt.lib") +# endif +# ifndef BCRYPT_USE_SYSTEM_PREFERRED_RNG +# define BCRYPT_USE_SYSTEM_PREFERRED_RNG 0x00000002 +# endif +# ifndef STATUS_SUCCESS +# define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +# endif +#elif defined(USE_WIN32_CRYPTO) +# include <wincrypt.h> +# ifdef _MSC_VER +# pragma comment(lib, "advapi32.lib") +# endif +#endif + +CURLcode Curl_win32_random(unsigned char *entropy, size_t length) +{ + memset(entropy, 0, length); + +#if defined(HAVE_WIN_BCRYPTGENRANDOM) + if(BCryptGenRandom(NULL, entropy, (ULONG)length, + BCRYPT_USE_SYSTEM_PREFERRED_RNG) != STATUS_SUCCESS) + return CURLE_FAILED_INIT; + + return CURLE_OK; +#elif defined(USE_WIN32_CRYPTO) + { + HCRYPTPROV hCryptProv = 0; + + if(!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + return CURLE_FAILED_INIT; + + if(!CryptGenRandom(hCryptProv, (DWORD)length, entropy)) { + CryptReleaseContext(hCryptProv, 0UL); + return CURLE_FAILED_INIT; + } + + CryptReleaseContext(hCryptProv, 0UL); + } + return CURLE_OK; +#else + return CURLE_NOT_BUILT_IN; +#endif +} +#endif + +static CURLcode randit(struct Curl_easy *data, unsigned int *rnd) +{ + CURLcode result = CURLE_OK; + static unsigned int randseed; + static bool seeded = FALSE; + +#ifdef CURLDEBUG + char *force_entropy = getenv("CURL_ENTROPY"); + if(force_entropy) { + if(!seeded) { + unsigned int seed = 0; + size_t elen = strlen(force_entropy); + size_t clen = sizeof(seed); + size_t min = elen < clen ? elen : clen; + memcpy((char *)&seed, force_entropy, min); + randseed = ntohl(seed); + seeded = TRUE; + } + else + randseed++; + *rnd = randseed; + return CURLE_OK; + } +#endif + + /* data may be NULL! */ + result = Curl_ssl_random(data, (unsigned char *)rnd, sizeof(*rnd)); + if(result != CURLE_NOT_BUILT_IN) + /* only if there is no random function in the TLS backend do the non crypto + version, otherwise return result */ + return result; + + /* ---- non-cryptographic version following ---- */ + +#ifdef _WIN32 + if(!seeded) { + result = Curl_win32_random((unsigned char *)rnd, sizeof(*rnd)); + if(result != CURLE_NOT_BUILT_IN) + return result; + } +#endif + +#if defined(HAVE_ARC4RANDOM) && !defined(USE_OPENSSL) + if(!seeded) { + *rnd = (unsigned int)arc4random(); + return CURLE_OK; + } +#endif + +#if defined(RANDOM_FILE) && !defined(_WIN32) + if(!seeded) { + /* if there's a random file to read a seed from, use it */ + int fd = open(RANDOM_FILE, O_RDONLY); + if(fd > -1) { + /* read random data into the randseed variable */ + ssize_t nread = read(fd, &randseed, sizeof(randseed)); + if(nread == sizeof(randseed)) + seeded = TRUE; + close(fd); + } + } +#endif + + if(!seeded) { + struct curltime now = Curl_now(); + infof(data, "WARNING: using weak random seed"); + randseed += (unsigned int)now.tv_usec + (unsigned int)now.tv_sec; + randseed = randseed * 1103515245 + 12345; + randseed = randseed * 1103515245 + 12345; + randseed = randseed * 1103515245 + 12345; + seeded = TRUE; + } + + { + unsigned int r; + /* Return an unsigned 32-bit pseudo-random number. */ + r = randseed = randseed * 1103515245 + 12345; + *rnd = (r << 16) | ((r >> 16) & 0xFFFF); + } + return CURLE_OK; +} + +/* + * Curl_rand() stores 'num' number of random unsigned characters in the buffer + * 'rnd' points to. + * + * If libcurl is built without TLS support or with a TLS backend that lacks a + * proper random API (rustls or mbedTLS), this function will use "weak" + * random. + * + * When built *with* TLS support and a backend that offers strong random, it + * will return error if it cannot provide strong random values. + * + * NOTE: 'data' may be passed in as NULL when coming from external API without + * easy handle! + * + */ + +CURLcode Curl_rand(struct Curl_easy *data, unsigned char *rnd, size_t num) +{ + CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; + + DEBUGASSERT(num > 0); + + while(num) { + unsigned int r; + size_t left = num < sizeof(unsigned int) ? num : sizeof(unsigned int); + + result = randit(data, &r); + if(result) + return result; + + while(left) { + *rnd++ = (unsigned char)(r & 0xFF); + r >>= 8; + --num; + --left; + } + } + + return result; +} + +/* + * Curl_rand_hex() fills the 'rnd' buffer with a given 'num' size with random + * hexadecimal digits PLUS a null-terminating byte. It must be an odd number + * size. + */ + +CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, + size_t num) +{ + CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; + unsigned char buffer[128]; + DEBUGASSERT(num > 1); + +#ifdef __clang_analyzer__ + /* This silences a scan-build warning about accessing this buffer with + uninitialized memory. */ + memset(buffer, 0, sizeof(buffer)); +#endif + + if((num/2 >= sizeof(buffer)) || !(num&1)) + /* make sure it fits in the local buffer and that it is an odd number! */ + return CURLE_BAD_FUNCTION_ARGUMENT; + + num--; /* save one for null-termination */ + + result = Curl_rand(data, buffer, num/2); + if(result) + return result; + + Curl_hexencode(buffer, num/2, rnd, num + 1); + return result; +} + +/* + * Curl_rand_alnum() fills the 'rnd' buffer with a given 'num' size with random + * alphanumerical chars PLUS a null-terminating byte. + */ + +static const char alnum[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +CURLcode Curl_rand_alnum(struct Curl_easy *data, unsigned char *rnd, + size_t num) +{ + CURLcode result = CURLE_OK; + const int alnumspace = sizeof(alnum) - 1; + unsigned int r; + DEBUGASSERT(num > 1); + + num--; /* save one for null-termination */ + + while(num) { + do { + result = randit(data, &r); + if(result) + return result; + } while(r >= (UINT_MAX - UINT_MAX % alnumspace)); + + *rnd++ = alnum[r % alnumspace]; + num--; + } + *rnd = 0; + + return result; +} diff --git a/Utilities/cmcurl/lib/rand.h b/Utilities/cmcurl/lib/rand.h new file mode 100644 index 0000000..bc05239 --- /dev/null +++ b/Utilities/cmcurl/lib/rand.h @@ -0,0 +1,50 @@ +#ifndef HEADER_CURL_RAND_H +#define HEADER_CURL_RAND_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +CURLcode Curl_rand(struct Curl_easy *data, unsigned char *rnd, size_t num); + +/* + * Curl_rand_hex() fills the 'rnd' buffer with a given 'num' size with random + * hexadecimal digits PLUS a null-terminating byte. It must be an odd number + * size. + */ +CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, + size_t num); + +/* + * Curl_rand_alnum() fills the 'rnd' buffer with a given 'num' size with random + * alphanumerical chars PLUS a null-terminating byte. + */ +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/rename.c b/Utilities/cmcurl/lib/rename.c new file mode 100644 index 0000000..4c88698 --- /dev/null +++ b/Utilities/cmcurl/lib/rename.c @@ -0,0 +1,73 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "rename.h" + +#include "curl_setup.h" + +#if (!defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_COOKIES)) || \ + !defined(CURL_DISABLE_ALTSVC) + +#include "curl_multibyte.h" +#include "timeval.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* return 0 on success, 1 on error */ +int Curl_rename(const char *oldpath, const char *newpath) +{ +#ifdef _WIN32 + /* rename() on Windows doesn't overwrite, so we can't use it here. + MoveFileEx() will overwrite and is usually atomic, however it fails + when there are open handles to the file. */ + const int max_wait_ms = 1000; + struct curltime start = Curl_now(); + TCHAR *tchar_oldpath = curlx_convert_UTF8_to_tchar((char *)oldpath); + TCHAR *tchar_newpath = curlx_convert_UTF8_to_tchar((char *)newpath); + for(;;) { + timediff_t diff; + if(MoveFileEx(tchar_oldpath, tchar_newpath, MOVEFILE_REPLACE_EXISTING)) { + curlx_unicodefree(tchar_oldpath); + curlx_unicodefree(tchar_newpath); + break; + } + diff = Curl_timediff(Curl_now(), start); + if(diff < 0 || diff > max_wait_ms) { + curlx_unicodefree(tchar_oldpath); + curlx_unicodefree(tchar_newpath); + return 1; + } + Sleep(1); + } +#else + if(rename(oldpath, newpath)) + return 1; +#endif + return 0; +} + +#endif diff --git a/Utilities/cmcurl/lib/rename.h b/Utilities/cmcurl/lib/rename.h new file mode 100644 index 0000000..0444082 --- /dev/null +++ b/Utilities/cmcurl/lib/rename.h @@ -0,0 +1,29 @@ +#ifndef HEADER_CURL_RENAME_H +#define HEADER_CURL_RENAME_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +int Curl_rename(const char *oldpath, const char *newpath); + +#endif /* HEADER_CURL_RENAME_H */ diff --git a/Utilities/cmcurl/lib/rtsp.c b/Utilities/cmcurl/lib/rtsp.c new file mode 100644 index 0000000..e673bb8 --- /dev/null +++ b/Utilities/cmcurl/lib/rtsp.c @@ -0,0 +1,1003 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_RTSP) && !defined(USE_HYPER) + +#include "urldata.h" +#include <curl/curl.h> +#include "transfer.h" +#include "sendf.h" +#include "multiif.h" +#include "http.h" +#include "url.h" +#include "progress.h" +#include "rtsp.h" +#include "strcase.h" +#include "select.h" +#include "connect.h" +#include "cfilters.h" +#include "strdup.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define RTP_PKT_LENGTH(p) ((((unsigned int)((unsigned char)((p)[2]))) << 8) | \ + ((unsigned int)((unsigned char)((p)[3])))) + +/* protocol-specific functions set up to be called by the main engine */ +static CURLcode rtsp_do(struct Curl_easy *data, bool *done); +static CURLcode rtsp_done(struct Curl_easy *data, CURLcode, bool premature); +static CURLcode rtsp_connect(struct Curl_easy *data, bool *done); +static CURLcode rtsp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead); +static int rtsp_getsock_do(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); + +/* + * Parse and write out any available RTP data. + * @param data the transfer + * @param conn the connection + * @param buf data read from connection + * @param blen amount of data in buf + * @param consumed out, number of blen consumed + * @param readmore out, TRUE iff complete buf was consumed and more data + * is needed + */ +static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, + struct connectdata *conn, + const char *buf, + size_t blen, + size_t *pconsumed, + bool *readmore); + +static CURLcode rtsp_setup_connection(struct Curl_easy *data, + struct connectdata *conn); +static unsigned int rtsp_conncheck(struct Curl_easy *data, + struct connectdata *check, + unsigned int checks_to_perform); + +/* this returns the socket to wait for in the DO and DOING state for the multi + interface and then we're always _sending_ a request and thus we wait for + the single socket to become writable only */ +static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *socks) +{ + /* write mode */ + (void)data; + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_WRITESOCK(0); +} + +static +CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len); +static +CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport); + + +/* + * RTSP handler interface. + */ +const struct Curl_handler Curl_handler_rtsp = { + "RTSP", /* scheme */ + rtsp_setup_connection, /* setup_connection */ + rtsp_do, /* do_it */ + rtsp_done, /* done */ + ZERO_NULL, /* do_more */ + rtsp_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + rtsp_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + rtsp_disconnect, /* disconnect */ + rtsp_rtp_readwrite, /* readwrite */ + rtsp_conncheck, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_RTSP, /* defport */ + CURLPROTO_RTSP, /* protocol */ + CURLPROTO_RTSP, /* family */ + PROTOPT_NONE /* flags */ +}; + +#define MAX_RTP_BUFFERSIZE 1000000 /* arbitrary */ + +static CURLcode rtsp_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + struct RTSP *rtsp; + (void)conn; + + data->req.p.rtsp = rtsp = calloc(1, sizeof(struct RTSP)); + if(!rtsp) + return CURLE_OUT_OF_MEMORY; + + Curl_dyn_init(&conn->proto.rtspc.buf, MAX_RTP_BUFFERSIZE); + return CURLE_OK; +} + + +/* + * Function to check on various aspects of a connection. + */ +static unsigned int rtsp_conncheck(struct Curl_easy *data, + struct connectdata *conn, + unsigned int checks_to_perform) +{ + unsigned int ret_val = CONNRESULT_NONE; + (void)data; + + if(checks_to_perform & CONNCHECK_ISDEAD) { + bool input_pending; + if(!Curl_conn_is_alive(data, conn, &input_pending)) + ret_val |= CONNRESULT_DEAD; + } + + return ret_val; +} + + +static CURLcode rtsp_connect(struct Curl_easy *data, bool *done) +{ + CURLcode httpStatus; + + httpStatus = Curl_http_connect(data, done); + + /* Initialize the CSeq if not already done */ + if(data->state.rtsp_next_client_CSeq == 0) + data->state.rtsp_next_client_CSeq = 1; + if(data->state.rtsp_next_server_CSeq == 0) + data->state.rtsp_next_server_CSeq = 1; + + data->conn->proto.rtspc.rtp_channel = -1; + + return httpStatus; +} + +static CURLcode rtsp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead) +{ + (void) dead; + (void) data; + Curl_dyn_free(&conn->proto.rtspc.buf); + return CURLE_OK; +} + + +static CURLcode rtsp_done(struct Curl_easy *data, + CURLcode status, bool premature) +{ + struct RTSP *rtsp = data->req.p.rtsp; + CURLcode httpStatus; + + /* Bypass HTTP empty-reply checks on receive */ + if(data->set.rtspreq == RTSPREQ_RECEIVE) + premature = TRUE; + + httpStatus = Curl_http_done(data, status, premature); + + if(rtsp && !status && !httpStatus) { + /* Check the sequence numbers */ + long CSeq_sent = rtsp->CSeq_sent; + long CSeq_recv = rtsp->CSeq_recv; + if((data->set.rtspreq != RTSPREQ_RECEIVE) && (CSeq_sent != CSeq_recv)) { + failf(data, + "The CSeq of this request %ld did not match the response %ld", + CSeq_sent, CSeq_recv); + return CURLE_RTSP_CSEQ_ERROR; + } + if(data->set.rtspreq == RTSPREQ_RECEIVE && + (data->conn->proto.rtspc.rtp_channel == -1)) { + infof(data, "Got an RTP Receive with a CSeq of %ld", CSeq_recv); + } + } + + return httpStatus; +} + +static CURLcode rtsp_do(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; + Curl_RtspReq rtspreq = data->set.rtspreq; + struct RTSP *rtsp = data->req.p.rtsp; + struct dynbuf req_buffer; + curl_off_t postsize = 0; /* for ANNOUNCE and SET_PARAMETER */ + curl_off_t putsize = 0; /* for ANNOUNCE and SET_PARAMETER */ + + 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_proxyuserpwd = NULL; + const char *p_userpwd = NULL; + + *done = TRUE; + + rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq; + rtsp->CSeq_recv = 0; + + /* Setup the first_* fields to allow auth details get sent + to this origin */ + + if(!data->state.first_host) { + data->state.first_host = strdup(conn->host.name); + if(!data->state.first_host) + return CURLE_OUT_OF_MEMORY; + + data->state.first_remote_port = conn->remote_port; + data->state.first_remote_protocol = conn->handler->protocol; + } + + /* 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 don't 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; + } + + if(rtspreq == RTSPREQ_RECEIVE) { + Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); + + return result; + } + + p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; + if(!p_session_id && + (rtspreq & ~(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | RTSPREQ_SETUP))) { + failf(data, "Refusing to issue an RTSP request [%s] without a session ID.", + p_request); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + /* 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]) { + Curl_safefree(data->state.aptr.rtsp_transport); + + data->state.aptr.rtsp_transport = + aprintf("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."); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + 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]) { + Curl_safefree(data->state.aptr.accept_encoding); + data->state.aptr.accept_encoding = + aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); + + if(!data->state.aptr.accept_encoding) + return CURLE_OUT_OF_MEMORY; + + p_accept_encoding = data->state.aptr.accept_encoding; + } + } + + /* The User-Agent string might have been allocated in url.c 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) { + Curl_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, FALSE); + if(result) + return result; + + p_proxyuserpwd = data->state.aptr.proxyuserpwd; + p_userpwd = data->state.aptr.userpwd; + + /* Referrer */ + Curl_safefree(data->state.aptr.ref); + if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer"))) + data->state.aptr.ref = aprintf("Referer: %s\r\n", 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) { + Curl_safefree(data->state.aptr.rangeline); + data->state.aptr.rangeline = aprintf("Range: %s\r\n", data->state.range); + p_range = data->state.aptr.rangeline; + } + } + + /* + * Sanity check the custom headers + */ + if(Curl_checkheaders(data, STRCONST("CSeq"))) { + failf(data, "CSeq cannot be set as a custom header."); + return CURLE_RTSP_CSEQ_ERROR; + } + if(Curl_checkheaders(data, STRCONST("Session"))) { + failf(data, "Session ID cannot be set as a custom header."); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + /* Initialize a dynamic send buffer */ + Curl_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER); + + result = + Curl_dyn_addf(&req_buffer, + "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ + "CSeq: %ld\r\n", /* CSeq */ + p_request, p_stream_uri, rtsp->CSeq_sent); + if(result) + return result; + + /* + * Rather than do a normal alloc line, keep the session_id unformatted + * to make comparison easier + */ + if(p_session_id) { + result = Curl_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id); + if(result) + return result; + } + + /* + * Shared HTTP-like options + */ + result = Curl_dyn_addf(&req_buffer, + "%s" /* transport */ + "%s" /* accept */ + "%s" /* accept-encoding */ + "%s" /* range */ + "%s" /* referrer */ + "%s" /* user-agent */ + "%s" /* proxyuserpwd */ + "%s" /* userpwd */ + , + 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_proxyuserpwd ? p_proxyuserpwd : "", + p_userpwd ? p_userpwd : ""); + + /* + * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM + * with basic and digest, it will be freed anyway by the next request + */ + Curl_safefree(data->state.aptr.userpwd); + + if(result) + return result; + + if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) { + result = Curl_add_timecondition(data, &req_buffer); + if(result) + return result; + } + + result = Curl_add_custom_headers(data, FALSE, &req_buffer); + if(result) + return result; + + if(rtspreq == RTSPREQ_ANNOUNCE || + rtspreq == RTSPREQ_SET_PARAMETER || + rtspreq == RTSPREQ_GET_PARAMETER) { + + if(data->state.upload) { + putsize = data->state.infilesize; + data->state.httpreq = HTTPREQ_PUT; + + } + else { + postsize = (data->state.infilesize != -1)? + data->state.infilesize: + (data->set.postfields? (curl_off_t)strlen(data->set.postfields):0); + data->state.httpreq = HTTPREQ_POST; + } + + if(putsize > 0 || postsize > 0) { + /* As stated in the http comments, it is probably not wise to + * actually set a custom Content-Length in the headers */ + if(!Curl_checkheaders(data, STRCONST("Content-Length"))) { + result = + Curl_dyn_addf(&req_buffer, + "Content-Length: %" CURL_FORMAT_CURL_OFF_T"\r\n", + (data->state.upload ? putsize : postsize)); + if(result) + return result; + } + + if(rtspreq == RTSPREQ_SET_PARAMETER || + rtspreq == RTSPREQ_GET_PARAMETER) { + if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { + result = Curl_dyn_addn(&req_buffer, + STRCONST("Content-Type: " + "text/parameters\r\n")); + if(result) + return result; + } + } + + if(rtspreq == RTSPREQ_ANNOUNCE) { + if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { + result = Curl_dyn_addn(&req_buffer, + STRCONST("Content-Type: " + "application/sdp\r\n")); + if(result) + return result; + } + } + + data->state.expect100header = FALSE; /* RTSP posts are simple/small */ + } + else if(rtspreq == RTSPREQ_GET_PARAMETER) { + /* Check for an empty GET_PARAMETER (heartbeat) request */ + data->state.httpreq = HTTPREQ_HEAD; + data->req.no_body = TRUE; + } + } + + /* RTSP never allows chunked transfer */ + data->req.forbidchunk = TRUE; + /* Finish the request buffer */ + result = Curl_dyn_addn(&req_buffer, STRCONST("\r\n")); + if(result) + return result; + + if(postsize > 0) { + result = Curl_dyn_addn(&req_buffer, data->set.postfields, + (size_t)postsize); + if(result) + return result; + } + + /* issue the request */ + result = Curl_buffer_send(&req_buffer, data, data->req.p.http, + &data->info.request_size, 0, FIRSTSOCKET); + if(result) { + failf(data, "Failed sending RTSP request"); + return result; + } + + Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, putsize?FIRSTSOCKET:-1); + + /* Increment the CSeq on success */ + data->state.rtsp_next_client_CSeq++; + + if(data->req.writebytecount) { + /* if a request-body has been sent off, we make sure this progress is + noted properly */ + Curl_pgrsSetUploadCounter(data, data->req.writebytecount); + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + } + + return result; +} + +static CURLcode rtsp_filter_rtp(struct Curl_easy *data, + struct connectdata *conn, + const char *buf, + size_t blen, + bool in_body, + size_t *pconsumed) +{ + struct rtsp_conn *rtspc = &(conn->proto.rtspc); + CURLcode result = CURLE_OK; + + *pconsumed = 0; + while(blen) { + switch(rtspc->state) { + + case RTP_PARSE_SKIP: { + DEBUGASSERT(Curl_dyn_len(&rtspc->buf) == 0); + if(in_body && buf[0] != '$') { + /* in BODY and no valid start, do not consume and return */ + goto out; + } + while(blen && buf[0] != '$') { + if(!in_body && buf[0] == 'R' && + data->set.rtspreq != RTSPREQ_RECEIVE) { + if(strncmp(buf, "RTSP/", (blen < 5) ? blen : 5) == 0) { + /* This could be the next response, no consume and return */ + if(*pconsumed) { + DEBUGF(infof(data, "RTP rtsp_filter_rtp[SKIP] RTSP/ prefix, " + "skipping %zd bytes of junk", *pconsumed)); + } + rtspc->state = RTP_PARSE_SKIP; + rtspc->in_header = TRUE; + goto out; + } + } + /* junk, consume without buffering */ + *pconsumed += 1; + ++buf; + --blen; + } + if(blen && buf[0] == '$') { + /* possible start of an RTP message, buffer */ + if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + *pconsumed += 1; + ++buf; + --blen; + rtspc->state = RTP_PARSE_CHANNEL; + } + break; + } + + case RTP_PARSE_CHANNEL: { + int idx = ((unsigned char)buf[0]) / 8; + int off = ((unsigned char)buf[0]) % 8; + DEBUGASSERT(Curl_dyn_len(&rtspc->buf) == 1); + if(!(data->state.rtp_channel_mask[idx] & (1 << off))) { + /* invalid channel number, junk or BODY data */ + rtspc->state = RTP_PARSE_SKIP; + if(in_body) { + /* we do not consume this byte, it is BODY data */ + DEBUGF(infof(data, "RTSP: invalid RTP channel %d in BODY, " + "treating as BODY data", idx)); + if(*pconsumed == 0) { + /* We did not consume the initial '$' in our buffer, but had + * it from an earlier call. We cannot un-consume it and have + * to write it directly as BODY data */ + result = Curl_client_write(data, CLIENTWRITE_BODY, + Curl_dyn_ptr(&rtspc->buf), 1); + Curl_dyn_free(&rtspc->buf); + if(result) + goto out; + } + else { + /* un-consume the '$' and leave */ + Curl_dyn_free(&rtspc->buf); + *pconsumed -= 1; + --buf; + ++blen; + goto out; + } + } + else { + /* not BODY, forget the junk '$'. Do not consume this byte, + * it might be a start */ + infof(data, "RTSP: invalid RTP channel %d, skipping", idx); + Curl_dyn_free(&rtspc->buf); + } + break; + } + /* a valid channel, so we expect this to be a real RTP message */ + rtspc->rtp_channel = (unsigned char)buf[0]; + if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + *pconsumed += 1; + ++buf; + --blen; + rtspc->state = RTP_PARSE_LEN; + break; + } + + case RTP_PARSE_LEN: { + size_t rtp_len = Curl_dyn_len(&rtspc->buf); + const char *rtp_buf; + DEBUGASSERT(rtp_len >= 2 && rtp_len < 4); + if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + *pconsumed += 1; + ++buf; + --blen; + if(rtp_len == 2) + break; + rtp_buf = Curl_dyn_ptr(&rtspc->buf); + rtspc->rtp_len = RTP_PKT_LENGTH(rtp_buf) + 4; + rtspc->state = RTP_PARSE_DATA; + break; + } + + case RTP_PARSE_DATA: { + size_t rtp_len = Curl_dyn_len(&rtspc->buf); + size_t needed; + DEBUGASSERT(rtp_len < rtspc->rtp_len); + needed = rtspc->rtp_len - rtp_len; + if(needed <= blen) { + if(Curl_dyn_addn(&rtspc->buf, buf, needed)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + *pconsumed += needed; + buf += needed; + blen -= needed; + /* complete RTP message in buffer */ + DEBUGF(infof(data, "RTP write channel %d rtp_len %zu", + rtspc->rtp_channel, rtspc->rtp_len)); + result = rtp_client_write(data, Curl_dyn_ptr(&rtspc->buf), + rtspc->rtp_len); + Curl_dyn_free(&rtspc->buf); + rtspc->state = RTP_PARSE_SKIP; + if(result) + goto out; + } + else { + if(Curl_dyn_addn(&rtspc->buf, buf, blen)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + *pconsumed += blen; + buf += blen; + blen = 0; + } + break; + } + + default: + DEBUGASSERT(0); + return CURLE_RECV_ERROR; + } + } +out: + return result; +} + +static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, + struct connectdata *conn, + const char *buf, + size_t blen, + size_t *pconsumed, + bool *readmore) +{ + struct rtsp_conn *rtspc = &(conn->proto.rtspc); + CURLcode result = CURLE_OK; + size_t consumed = 0; + bool in_body; + + if(!data->req.header) + rtspc->in_header = FALSE; + in_body = (data->req.headerline && !rtspc->in_header) && + (data->req.size >= 0) && + (data->req.bytecount < data->req.size); + + *readmore = FALSE; + *pconsumed = 0; + if(!blen) { + goto out; + } + + /* If header parsing is not onging, extract RTP messages */ + if(!rtspc->in_header) { + result = rtsp_filter_rtp(data, conn, buf, blen, in_body, &consumed); + if(result) + goto out; + *pconsumed += consumed; + buf += consumed; + blen -= consumed; + } + + /* we want to parse headers, do so */ + if(data->req.header && blen) { + rtspc->in_header = TRUE; + result = Curl_http_readwrite_headers(data, conn, buf, blen, + &consumed); + if(result) + goto out; + + *pconsumed += consumed; + buf += consumed; + blen -= consumed; + + if(!data->req.header) + rtspc->in_header = FALSE; + + if(!rtspc->in_header) { + /* If header parsing is done and data left, extract RTP messages */ + in_body = (data->req.headerline && !rtspc->in_header) && + (data->req.size >= 0) && + (data->req.bytecount < data->req.size); + result = rtsp_filter_rtp(data, conn, buf, blen, in_body, &consumed); + if(result) + goto out; + *pconsumed += consumed; + } + } + + if(rtspc->state != RTP_PARSE_SKIP) + *readmore = TRUE; + +out: + if(!*readmore && data->set.rtspreq == RTSPREQ_RECEIVE) { + /* In special mode RECEIVE, we just process one chunk of network + * data, so we stop the transfer here, if we have no incomplete + * RTP message pending. */ + data->req.keepon &= ~KEEP_RECV; + } + return result; +} + +static +CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len) +{ + size_t wrote; + curl_write_callback writeit; + void *user_ptr; + + if(len == 0) { + failf(data, "Cannot write a 0 size RTP packet."); + return CURLE_WRITE_ERROR; + } + + /* If the user has configured CURLOPT_INTERLEAVEFUNCTION then use that + function and any configured CURLOPT_INTERLEAVEDATA to write out the RTP + data. Otherwise, use the CURLOPT_WRITEFUNCTION with the CURLOPT_WRITEDATA + pointer to write out the RTP data. */ + if(data->set.fwrite_rtp) { + writeit = data->set.fwrite_rtp; + user_ptr = data->set.rtp_out; + } + else { + writeit = data->set.fwrite_func; + user_ptr = data->set.out; + } + + Curl_set_in_callback(data, true); + wrote = writeit((char *)ptr, 1, len, user_ptr); + Curl_set_in_callback(data, false); + + if(CURL_WRITEFUNC_PAUSE == wrote) { + failf(data, "Cannot pause RTP"); + return CURLE_WRITE_ERROR; + } + + if(wrote != len) { + failf(data, "Failed writing RTP data"); + return CURLE_WRITE_ERROR; + } + + return CURLE_OK; +} + +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) +{ + if(checkprefix("CSeq:", header)) { + long CSeq = 0; + char *endp; + char *p = &header[5]; + while(ISBLANK(*p)) + p++; + CSeq = strtol(p, &endp, 10); + if(p != endp) { + struct RTSP *rtsp = data->req.p.rtsp; + rtsp->CSeq_recv = CSeq; /* mark the request */ + data->state.rtsp_CSeq_recv = CSeq; /* update the handle */ + } + else { + failf(data, "Unable to read the CSeq header: [%s]", header); + return CURLE_RTSP_CSEQ_ERROR; + } + } + else if(checkprefix("Session:", header)) { + char *start; + char *end; + size_t idlen; + + /* Find the first non-space letter */ + start = header + 8; + while(*start && ISBLANK(*start)) + start++; + + if(!*start) { + failf(data, "Got a blank Session ID"); + return CURLE_RTSP_SESSION_ERROR; + } + + /* Find the end of Session ID + * + * Allow any non whitespace content, up to the field separator or end of + * line. RFC 2326 isn't 100% clear on the session ID and for example + * gstreamer does url-encoded session ID's not covered by the standard. + */ + end = start; + while(*end && *end != ';' && !ISSPACE(*end)) + end++; + idlen = end - start; + + if(data->set.str[STRING_RTSP_SESSION_ID]) { + + /* 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) != 0) { + failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]", + start, data->set.str[STRING_RTSP_SESSION_ID]); + 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] = malloc(idlen + 1); + if(!data->set.str[STRING_RTSP_SESSION_ID]) + return CURLE_OUT_OF_MEMORY; + memcpy(data->set.str[STRING_RTSP_SESSION_ID], start, idlen); + (data->set.str[STRING_RTSP_SESSION_ID])[idlen] = '\0'; + } + } + else if(checkprefix("Transport:", header)) { + CURLcode result; + result = rtsp_parse_transport(data, header + 10); + if(result) + return result; + } + return CURLE_OK; +} + +static +CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport) +{ + /* If we receive multiple Transport response-headers, the linterleaved + channels of each response header is recorded and used together for + subsequent data validity checks.*/ + /* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */ + char *start; + char *end; + start = transport; + while(start && *start) { + while(*start && ISBLANK(*start) ) + start++; + end = strchr(start, ';'); + if(checkprefix("interleaved=", start)) { + long chan1, chan2, chan; + char *endp; + char *p = start + 12; + chan1 = strtol(p, &endp, 10); + if(p != endp && chan1 >= 0 && chan1 <= 255) { + unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; + chan2 = chan1; + if(*endp == '-') { + p = endp + 1; + chan2 = strtol(p, &endp, 10); + if(p == endp || chan2 < 0 || chan2 > 255) { + infof(data, "Unable to read the interleaved parameter from " + "Transport header: [%s]", transport); + chan2 = chan1; + } + } + for(chan = chan1; chan <= chan2; chan++) { + long idx = chan / 8; + long off = chan % 8; + rtp_channel_mask[idx] |= (unsigned char)(1 << off); + } + } + else { + infof(data, "Unable to read the interleaved parameter from " + "Transport header: [%s]", transport); + } + break; + } + /* skip to next parameter */ + start = (!end) ? end : (end + 1); + } + return CURLE_OK; +} + + +#endif /* CURL_DISABLE_RTSP or using Hyper */ diff --git a/Utilities/cmcurl/lib/rtsp.h b/Utilities/cmcurl/lib/rtsp.h new file mode 100644 index 0000000..237b80f --- /dev/null +++ b/Utilities/cmcurl/lib/rtsp.h @@ -0,0 +1,80 @@ +#ifndef HEADER_CURL_RTSP_H +#define HEADER_CURL_RTSP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#ifdef USE_HYPER +#define CURL_DISABLE_RTSP 1 +#endif + +#ifndef CURL_DISABLE_RTSP + +extern const struct Curl_handler Curl_handler_rtsp; + +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header); + +#else +/* disabled */ +#define Curl_rtsp_parseheader(x,y) CURLE_NOT_BUILT_IN + +#endif /* CURL_DISABLE_RTSP */ + +typedef enum { + RTP_PARSE_SKIP, + RTP_PARSE_CHANNEL, + RTP_PARSE_LEN, + RTP_PARSE_DATA +} rtp_parse_st; +/* + * RTSP Connection data + * + * Currently, only used for tracking incomplete RTP data reads + */ +struct rtsp_conn { + struct dynbuf buf; + int rtp_channel; + size_t rtp_len; + rtp_parse_st state; + BIT(in_header); +}; + +/**************************************************************************** + * RTSP unique setup + ***************************************************************************/ +struct RTSP { + /* + * http_wrapper MUST be the first element of this structure for the wrap + * logic to work. In this way, we get a cheap polymorphism because + * &(data->state.proto.rtsp) == &(data->state.proto.http) per the C spec + * + * HTTP functions can safely treat this as an HTTP struct, but RTSP aware + * functions can also index into the later elements. + */ + struct HTTP http_wrapper; /* wrap HTTP to do the heavy lifting */ + + long CSeq_sent; /* CSeq of this request */ + long CSeq_recv; /* CSeq received */ +}; + + +#endif /* HEADER_CURL_RTSP_H */ diff --git a/Utilities/cmcurl/lib/select.c b/Utilities/cmcurl/lib/select.c new file mode 100644 index 0000000..d92e745 --- /dev/null +++ b/Utilities/cmcurl/lib/select.c @@ -0,0 +1,403 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <limits.h> + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#elif defined(HAVE_UNISTD_H) +#include <unistd.h> +#endif + +#if !defined(HAVE_SELECT) && !defined(HAVE_POLL_FINE) +#error "We can't compile without select() or poll() support." +#endif + +#ifdef MSDOS +#include <dos.h> /* delay() */ +#endif + +#include <curl/curl.h> + +#include "urldata.h" +#include "connect.h" +#include "select.h" +#include "timediff.h" +#include "warnless.h" + +/* + * Internal function used for waiting a specific amount of ms + * in Curl_socket_check() and Curl_poll() when no file descriptor + * is provided to wait on, just being used to delay execution. + * WinSock select() and poll() timeout mechanisms need a valid + * socket descriptor in a not null file descriptor set to work. + * Waiting indefinitely with this function is not allowed, a + * zero or negative timeout value will return immediately. + * Timeout resolution, accuracy, as well as maximum supported + * value is system dependent, neither factor is a critical issue + * for the intended use of this function in the library. + * + * Return values: + * -1 = system call error, or invalid timeout value + * 0 = specified timeout has elapsed, or interrupted + */ +int Curl_wait_ms(timediff_t timeout_ms) +{ + int r = 0; + + if(!timeout_ms) + return 0; + if(timeout_ms < 0) { + SET_SOCKERRNO(EINVAL); + return -1; + } +#if defined(MSDOS) + delay(timeout_ms); +#elif defined(_WIN32) + /* prevent overflow, timeout_ms is typecast to ULONG/DWORD. */ +#if TIMEDIFF_T_MAX >= ULONG_MAX + if(timeout_ms >= ULONG_MAX) + timeout_ms = ULONG_MAX-1; + /* don't use ULONG_MAX, because that is equal to INFINITE */ +#endif + Sleep((ULONG)timeout_ms); +#else +#if defined(HAVE_POLL_FINE) + /* prevent overflow, timeout_ms is typecast to int. */ +#if TIMEDIFF_T_MAX > INT_MAX + if(timeout_ms > INT_MAX) + timeout_ms = INT_MAX; +#endif + r = poll(NULL, 0, (int)timeout_ms); +#else + { + struct timeval pending_tv; + r = select(0, NULL, NULL, NULL, curlx_mstotv(&pending_tv, timeout_ms)); + } +#endif /* HAVE_POLL_FINE */ +#endif /* USE_WINSOCK */ + if(r) { + if((r == -1) && (SOCKERRNO == EINTR)) + /* make EINTR from select or poll not a "lethal" error */ + r = 0; + else + r = -1; + } + return r; +} + +#ifndef HAVE_POLL_FINE +/* + * This is a wrapper around select() to aid in Windows compatibility. + * A negative timeout value makes this function wait indefinitely, + * unless no valid file descriptor is given, when this happens the + * negative timeout is ignored and the function times out immediately. + * + * Return values: + * -1 = system call error or fd >= FD_SETSIZE + * 0 = timeout + * N = number of signalled file descriptors + */ +static int our_select(curl_socket_t maxfd, /* highest socket number */ + fd_set *fds_read, /* sockets ready for reading */ + fd_set *fds_write, /* sockets ready for writing */ + fd_set *fds_err, /* sockets with errors */ + timediff_t timeout_ms) /* milliseconds to wait */ +{ + struct timeval pending_tv; + struct timeval *ptimeout; + +#ifdef USE_WINSOCK + /* WinSock select() can't handle zero events. See the comment below. */ + if((!fds_read || fds_read->fd_count == 0) && + (!fds_write || fds_write->fd_count == 0) && + (!fds_err || fds_err->fd_count == 0)) { + /* no sockets, just wait */ + return Curl_wait_ms(timeout_ms); + } +#endif + + ptimeout = curlx_mstotv(&pending_tv, timeout_ms); + +#ifdef USE_WINSOCK + /* WinSock select() must not be called with an fd_set that contains zero + fd flags, or it will return WSAEINVAL. But, it also can't be called + with no fd_sets at all! From the documentation: + + Any two of the parameters, readfds, writefds, or exceptfds, can be + given as null. At least one must be non-null, and any non-null + descriptor set must contain at least one handle to a socket. + + It is unclear why WinSock doesn't just handle this for us instead of + calling this an error. Luckily, with WinSock, we can _also_ ask how + many bits are set on an fd_set. So, let's just check it beforehand. + */ + return select((int)maxfd + 1, + fds_read && fds_read->fd_count ? fds_read : NULL, + fds_write && fds_write->fd_count ? fds_write : NULL, + fds_err && fds_err->fd_count ? fds_err : NULL, ptimeout); +#else + return select((int)maxfd + 1, fds_read, fds_write, fds_err, ptimeout); +#endif +} + +#endif + +/* + * Wait for read or write events on a set of file descriptors. It uses poll() + * when a fine poll() is available, in order to avoid limits with FD_SETSIZE, + * otherwise select() is used. An error is returned if select() is being used + * and a file descriptor is too large for FD_SETSIZE. + * + * A negative timeout value makes this function wait indefinitely, + * unless no valid file descriptor is given, when this happens the + * negative timeout is ignored and the function times out immediately. + * + * Return values: + * -1 = system call error or fd >= FD_SETSIZE + * 0 = timeout + * [bitmask] = action as described below + * + * CURL_CSELECT_IN - first socket is readable + * CURL_CSELECT_IN2 - second socket is readable + * CURL_CSELECT_OUT - write socket is writable + * CURL_CSELECT_ERR - an error condition occurred + */ +int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ + curl_socket_t readfd1, + curl_socket_t writefd, /* socket to write to */ + timediff_t timeout_ms) /* milliseconds to wait */ +{ + struct pollfd pfd[3]; + int num; + int r; + + if((readfd0 == CURL_SOCKET_BAD) && (readfd1 == CURL_SOCKET_BAD) && + (writefd == CURL_SOCKET_BAD)) { + /* no sockets, just wait */ + return Curl_wait_ms(timeout_ms); + } + + /* Avoid initial timestamp, avoid Curl_now() call, when elapsed + time in this function does not need to be measured. This happens + when function is called with a zero timeout or a negative timeout + value indicating a blocking call should be performed. */ + + num = 0; + if(readfd0 != CURL_SOCKET_BAD) { + pfd[num].fd = readfd0; + pfd[num].events = POLLRDNORM|POLLIN|POLLRDBAND|POLLPRI; + pfd[num].revents = 0; + num++; + } + if(readfd1 != CURL_SOCKET_BAD) { + pfd[num].fd = readfd1; + pfd[num].events = POLLRDNORM|POLLIN|POLLRDBAND|POLLPRI; + pfd[num].revents = 0; + num++; + } + if(writefd != CURL_SOCKET_BAD) { + pfd[num].fd = writefd; + pfd[num].events = POLLWRNORM|POLLOUT|POLLPRI; + pfd[num].revents = 0; + num++; + } + + r = Curl_poll(pfd, num, timeout_ms); + if(r <= 0) + return r; + + r = 0; + num = 0; + if(readfd0 != CURL_SOCKET_BAD) { + if(pfd[num].revents & (POLLRDNORM|POLLIN|POLLERR|POLLHUP)) + r |= CURL_CSELECT_IN; + if(pfd[num].revents & (POLLPRI|POLLNVAL)) + r |= CURL_CSELECT_ERR; + num++; + } + if(readfd1 != CURL_SOCKET_BAD) { + if(pfd[num].revents & (POLLRDNORM|POLLIN|POLLERR|POLLHUP)) + r |= CURL_CSELECT_IN2; + if(pfd[num].revents & (POLLPRI|POLLNVAL)) + r |= CURL_CSELECT_ERR; + num++; + } + if(writefd != CURL_SOCKET_BAD) { + if(pfd[num].revents & (POLLWRNORM|POLLOUT)) + r |= CURL_CSELECT_OUT; + if(pfd[num].revents & (POLLERR|POLLHUP|POLLPRI|POLLNVAL)) + r |= CURL_CSELECT_ERR; + } + + return r; +} + +/* + * This is a wrapper around poll(). If poll() does not exist, then + * select() is used instead. An error is returned if select() is + * being used and a file descriptor is too large for FD_SETSIZE. + * A negative timeout value makes this function wait indefinitely, + * unless no valid file descriptor is given, when this happens the + * negative timeout is ignored and the function times out immediately. + * + * Return values: + * -1 = system call error or fd >= FD_SETSIZE + * 0 = timeout + * N = number of structures with non zero revent fields + */ +int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) +{ +#ifdef HAVE_POLL_FINE + int pending_ms; +#else + fd_set fds_read; + fd_set fds_write; + fd_set fds_err; + curl_socket_t maxfd; +#endif + bool fds_none = TRUE; + unsigned int i; + int r; + + if(ufds) { + for(i = 0; i < nfds; i++) { + if(ufds[i].fd != CURL_SOCKET_BAD) { + fds_none = FALSE; + break; + } + } + } + if(fds_none) { + /* no sockets, just wait */ + return Curl_wait_ms(timeout_ms); + } + + /* Avoid initial timestamp, avoid Curl_now() call, when elapsed + time in this function does not need to be measured. This happens + when function is called with a zero timeout or a negative timeout + value indicating a blocking call should be performed. */ + +#ifdef HAVE_POLL_FINE + + /* prevent overflow, timeout_ms is typecast to int. */ +#if TIMEDIFF_T_MAX > INT_MAX + if(timeout_ms > INT_MAX) + timeout_ms = INT_MAX; +#endif + if(timeout_ms > 0) + pending_ms = (int)timeout_ms; + else if(timeout_ms < 0) + pending_ms = -1; + else + pending_ms = 0; + r = poll(ufds, nfds, pending_ms); + if(r <= 0) { + if((r == -1) && (SOCKERRNO == EINTR)) + /* make EINTR from select or poll not a "lethal" error */ + r = 0; + return r; + } + + for(i = 0; i < nfds; i++) { + if(ufds[i].fd == CURL_SOCKET_BAD) + continue; + if(ufds[i].revents & POLLHUP) + ufds[i].revents |= POLLIN; + if(ufds[i].revents & POLLERR) + ufds[i].revents |= POLLIN|POLLOUT; + } + +#else /* HAVE_POLL_FINE */ + + FD_ZERO(&fds_read); + FD_ZERO(&fds_write); + FD_ZERO(&fds_err); + maxfd = (curl_socket_t)-1; + + for(i = 0; i < nfds; i++) { + ufds[i].revents = 0; + if(ufds[i].fd == CURL_SOCKET_BAD) + continue; + VERIFY_SOCK(ufds[i].fd); + if(ufds[i].events & (POLLIN|POLLOUT|POLLPRI| + POLLRDNORM|POLLWRNORM|POLLRDBAND)) { + if(ufds[i].fd > maxfd) + maxfd = ufds[i].fd; + if(ufds[i].events & (POLLRDNORM|POLLIN)) + FD_SET(ufds[i].fd, &fds_read); + if(ufds[i].events & (POLLWRNORM|POLLOUT)) + FD_SET(ufds[i].fd, &fds_write); + if(ufds[i].events & (POLLRDBAND|POLLPRI)) + FD_SET(ufds[i].fd, &fds_err); + } + } + + /* + Note also that WinSock ignores the first argument, so we don't 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). + */ + r = our_select(maxfd, &fds_read, &fds_write, &fds_err, timeout_ms); + if(r <= 0) { + if((r == -1) && (SOCKERRNO == EINTR)) + /* make EINTR from select or poll not a "lethal" error */ + r = 0; + return r; + } + + r = 0; + for(i = 0; i < nfds; i++) { + ufds[i].revents = 0; + if(ufds[i].fd == CURL_SOCKET_BAD) + continue; + if(FD_ISSET(ufds[i].fd, &fds_read)) { + if(ufds[i].events & POLLRDNORM) + ufds[i].revents |= POLLRDNORM; + if(ufds[i].events & POLLIN) + ufds[i].revents |= POLLIN; + } + if(FD_ISSET(ufds[i].fd, &fds_write)) { + if(ufds[i].events & POLLWRNORM) + ufds[i].revents |= POLLWRNORM; + if(ufds[i].events & POLLOUT) + ufds[i].revents |= POLLOUT; + } + if(FD_ISSET(ufds[i].fd, &fds_err)) { + if(ufds[i].events & POLLRDBAND) + ufds[i].revents |= POLLRDBAND; + if(ufds[i].events & POLLPRI) + ufds[i].revents |= POLLPRI; + } + if(ufds[i].revents) + r++; + } + +#endif /* HAVE_POLL_FINE */ + + return r; +} diff --git a/Utilities/cmcurl/lib/select.h b/Utilities/cmcurl/lib/select.h new file mode 100644 index 0000000..5b1ca23 --- /dev/null +++ b/Utilities/cmcurl/lib/select.h @@ -0,0 +1,114 @@ +#ifndef HEADER_CURL_SELECT_H +#define HEADER_CURL_SELECT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_POLL_H +#include <poll.h> +#elif defined(HAVE_SYS_POLL_H) +#include <sys/poll.h> +#endif + +/* + * Definition of pollfd struct and constants for platforms lacking them. + */ + +#if !defined(HAVE_SYS_POLL_H) && \ + !defined(HAVE_POLL_H) && \ + !defined(POLLIN) + +#define POLLIN 0x01 +#define POLLPRI 0x02 +#define POLLOUT 0x04 +#define POLLERR 0x08 +#define POLLHUP 0x10 +#define POLLNVAL 0x20 + +struct pollfd +{ + curl_socket_t fd; + short events; + short revents; +}; + +#endif + +#ifndef POLLRDNORM +#define POLLRDNORM POLLIN +#endif + +#ifndef POLLWRNORM +#define POLLWRNORM POLLOUT +#endif + +#ifndef POLLRDBAND +#define POLLRDBAND POLLPRI +#endif + +/* there are three CSELECT defines that are defined in the public header that + are exposed to users, but this *IN2 bit is only ever used internally and + therefore defined here */ +#define CURL_CSELECT_IN2 (CURL_CSELECT_ERR << 1) + +int Curl_socket_check(curl_socket_t readfd, curl_socket_t readfd2, + curl_socket_t writefd, + timediff_t timeout_ms); +#define SOCKET_READABLE(x,z) \ + Curl_socket_check(x, CURL_SOCKET_BAD, CURL_SOCKET_BAD, z) +#define SOCKET_WRITABLE(x,z) \ + Curl_socket_check(CURL_SOCKET_BAD, CURL_SOCKET_BAD, x, z) + +int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms); +int Curl_wait_ms(timediff_t timeout_ms); + +/* + With Winsock the valid range is [0..INVALID_SOCKET-1] according to + https://docs.microsoft.com/en-us/windows/win32/winsock/socket-data-type-2 +*/ +#ifdef USE_WINSOCK +#define VALID_SOCK(s) ((s) < INVALID_SOCKET) +#define FDSET_SOCK(x) 1 +#define VERIFY_SOCK(x) do { \ + if(!VALID_SOCK(x)) { \ + SET_SOCKERRNO(WSAEINVAL); \ + return -1; \ + } \ +} while(0) +#else +#define VALID_SOCK(s) ((s) >= 0) + +/* If the socket is small enough to get set or read from an fdset */ +#define FDSET_SOCK(s) ((s) < FD_SETSIZE) + +#define VERIFY_SOCK(x) do { \ + if(!VALID_SOCK(x) || !FDSET_SOCK(x)) { \ + SET_SOCKERRNO(EINVAL); \ + return -1; \ + } \ + } while(0) +#endif + +#endif /* HEADER_CURL_SELECT_H */ diff --git a/Utilities/cmcurl/lib/sendf.c b/Utilities/cmcurl/lib/sendf.c new file mode 100644 index 0000000..a2fac0c --- /dev/null +++ b/Utilities/cmcurl/lib/sendf.c @@ -0,0 +1,802 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#ifdef HAVE_LINUX_TCP_H +#include <linux/tcp.h> +#elif defined(HAVE_NETINET_TCP_H) +#include <netinet/tcp.h> +#endif + +#include <curl/curl.h> + +#include "urldata.h" +#include "sendf.h" +#include "cfilters.h" +#include "connect.h" +#include "content_encoding.h" +#include "vtls/vtls.h" +#include "vssh/ssh.h" +#include "easyif.h" +#include "multiif.h" +#include "strerror.h" +#include "select.h" +#include "strdup.h" +#include "http2.h" +#include "headers.h" +#include "progress.h" +#include "ws.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +static CURLcode do_init_stack(struct Curl_easy *data); + +#if defined(CURL_DO_LINEEND_CONV) && !defined(CURL_DISABLE_FTP) +/* + * convert_lineends() changes CRLF (\r\n) end-of-line markers to a single LF + * (\n), with special processing for CRLF sequences that are split between two + * blocks of data. Remaining, bare CRs are changed to LFs. The possibly new + * size of the data is returned. + */ +static size_t convert_lineends(struct Curl_easy *data, + char *startPtr, size_t size) +{ + char *inPtr, *outPtr; + + /* sanity check */ + if(!startPtr || (size < 1)) { + return size; + } + + if(data->state.prev_block_had_trailing_cr) { + /* The previous block of incoming data + had a trailing CR, which was turned into a LF. */ + if(*startPtr == '\n') { + /* This block of incoming data starts with the + previous block's LF so get rid of it */ + memmove(startPtr, startPtr + 1, size-1); + size--; + /* and it wasn't a bare CR but a CRLF conversion instead */ + data->state.crlf_conversions++; + } + data->state.prev_block_had_trailing_cr = FALSE; /* reset the flag */ + } + + /* find 1st CR, if any */ + inPtr = outPtr = memchr(startPtr, '\r', size); + if(inPtr) { + /* at least one CR, now look for CRLF */ + while(inPtr < (startPtr + size-1)) { + /* note that it's size-1, so we'll never look past the last byte */ + if(memcmp(inPtr, "\r\n", 2) == 0) { + /* CRLF found, bump past the CR and copy the NL */ + inPtr++; + *outPtr = *inPtr; + /* keep track of how many CRLFs we converted */ + data->state.crlf_conversions++; + } + else { + if(*inPtr == '\r') { + /* lone CR, move LF instead */ + *outPtr = '\n'; + } + else { + /* not a CRLF nor a CR, just copy whatever it is */ + *outPtr = *inPtr; + } + } + outPtr++; + inPtr++; + } /* end of while loop */ + + if(inPtr < startPtr + size) { + /* handle last byte */ + if(*inPtr == '\r') { + /* deal with a CR at the end of the buffer */ + *outPtr = '\n'; /* copy a NL instead */ + /* note that a CRLF might be split across two blocks */ + data->state.prev_block_had_trailing_cr = TRUE; + } + else { + /* copy last byte */ + *outPtr = *inPtr; + } + outPtr++; + } + if(outPtr < startPtr + size) + /* tidy up by null terminating the now shorter data */ + *outPtr = '\0'; + + return (outPtr - startPtr); + } + return size; +} +#endif /* CURL_DO_LINEEND_CONV && !CURL_DISABLE_FTP */ + +/* + * Curl_nwrite() is an internal write function that sends data to the + * server. Works with a socket index for the connection. + * + * If the write would block (CURLE_AGAIN), it returns CURLE_OK and + * (*nwritten == 0). Otherwise we return regular CURLcode value. + */ +CURLcode Curl_nwrite(struct Curl_easy *data, + int sockindex, + const void *buf, + size_t blen, + ssize_t *pnwritten) +{ + ssize_t nwritten; + CURLcode result = CURLE_OK; + struct connectdata *conn; + + DEBUGASSERT(sockindex >= 0 && sockindex < 2); + DEBUGASSERT(pnwritten); + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + conn = data->conn; +#ifdef CURLDEBUG + { + /* Allow debug builds to override this logic to force short sends + */ + char *p = getenv("CURL_SMALLSENDS"); + if(p) { + size_t altsize = (size_t)strtoul(p, NULL, 10); + if(altsize) + blen = CURLMIN(blen, altsize); + } + } +#endif + nwritten = conn->send[sockindex](data, sockindex, buf, blen, &result); + if(result == CURLE_AGAIN) { + nwritten = 0; + result = CURLE_OK; + } + else if(result) { + nwritten = -1; /* make sure */ + } + else { + DEBUGASSERT(nwritten >= 0); + } + + *pnwritten = nwritten; + return result; +} + +/* + * Curl_write() is an internal write function that sends data to the + * server. Works with plain sockets, SCP, SSL or kerberos. + * + * If the write would block (CURLE_AGAIN), we return CURLE_OK and + * (*written == 0). Otherwise we return regular CURLcode value. + */ +CURLcode Curl_write(struct Curl_easy *data, + curl_socket_t sockfd, + const void *mem, + size_t len, + ssize_t *written) +{ + struct connectdata *conn; + int num; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + conn = data->conn; + num = (sockfd != CURL_SOCKET_BAD && sockfd == conn->sock[SECONDARYSOCKET]); + return Curl_nwrite(data, num, mem, len, written); +} + +static CURLcode pausewrite(struct Curl_easy *data, + int type, /* what type of data */ + bool paused_body, + const char *ptr, + size_t len) +{ + /* signalled to pause sending on this connection, but since we have data + we want to send we need to dup it to save a copy for when the sending + is again enabled */ + struct SingleRequest *k = &data->req; + struct UrlState *s = &data->state; + unsigned int i; + bool newtype = TRUE; + + Curl_conn_ev_data_pause(data, TRUE); + + if(s->tempcount) { + for(i = 0; i< s->tempcount; i++) { + if(s->tempwrite[i].type == type && + !!s->tempwrite[i].paused_body == !!paused_body) { + /* data for this type exists */ + newtype = FALSE; + break; + } + } + DEBUGASSERT(i < 3); + if(i >= 3) + /* There are more types to store than what fits: very bad */ + return CURLE_OUT_OF_MEMORY; + } + else + i = 0; + + if(newtype) { + /* store this information in the state struct for later use */ + Curl_dyn_init(&s->tempwrite[i].b, DYN_PAUSE_BUFFER); + s->tempwrite[i].type = type; + s->tempwrite[i].paused_body = paused_body; + s->tempcount++; + } + + if(Curl_dyn_addn(&s->tempwrite[i].b, (unsigned char *)ptr, len)) + return CURLE_OUT_OF_MEMORY; + + /* mark the connection as RECV paused */ + k->keepon |= KEEP_RECV_PAUSE; + + return CURLE_OK; +} + + +/* chop_write() writes chunks of data not larger than CURL_MAX_WRITE_SIZE via + * client write callback(s) and takes care of pause requests from the + * callbacks. + */ +static CURLcode chop_write(struct Curl_easy *data, + int type, + bool skip_body_write, + char *optr, + size_t olen) +{ + struct connectdata *conn = data->conn; + curl_write_callback writeheader = NULL; + curl_write_callback writebody = NULL; + char *ptr = optr; + size_t len = olen; + void *writebody_ptr = data->set.out; + + if(!len) + return CURLE_OK; + + /* If reading is paused, append this data to the already held data for this + type. */ + if(data->req.keepon & KEEP_RECV_PAUSE) + return pausewrite(data, type, !skip_body_write, ptr, len); + + /* Determine the callback(s) to use. */ + if(!skip_body_write && + ((type & CLIENTWRITE_BODY) || + ((type & CLIENTWRITE_HEADER) && data->set.include_header))) { +#ifdef USE_WEBSOCKETS + if(conn->handler->protocol & (CURLPROTO_WS|CURLPROTO_WSS)) { + writebody = Curl_ws_writecb; + writebody_ptr = data; + } + else +#endif + writebody = data->set.fwrite_func; + } + if((type & (CLIENTWRITE_HEADER|CLIENTWRITE_INFO)) && + (data->set.fwrite_header || data->set.writeheader)) { + /* + * Write headers to the same callback or to the especially setup + * header callback function (added after version 7.7.1). + */ + writeheader = + data->set.fwrite_header? data->set.fwrite_header: data->set.fwrite_func; + } + + /* Chop data, write chunks. */ + while(len) { + size_t chunklen = len <= CURL_MAX_WRITE_SIZE? len: CURL_MAX_WRITE_SIZE; + + if(writebody) { + size_t wrote; + Curl_set_in_callback(data, true); + wrote = writebody(ptr, 1, chunklen, writebody_ptr); + Curl_set_in_callback(data, false); + + if(CURL_WRITEFUNC_PAUSE == wrote) { + if(conn->handler->flags & PROTOPT_NONETWORK) { + /* Protocols that work without network cannot be paused. This is + actually only FILE:// just now, and it can't pause since the + transfer isn't done using the "normal" procedure. */ + failf(data, "Write callback asked for PAUSE when not supported"); + return CURLE_WRITE_ERROR; + } + return pausewrite(data, type, TRUE, ptr, len); + } + if(wrote != chunklen) { + failf(data, "Failure writing output to destination"); + return CURLE_WRITE_ERROR; + } + } + + ptr += chunklen; + len -= chunklen; + } + +#ifndef CURL_DISABLE_HTTP + /* HTTP header, but not status-line */ + if((conn->handler->protocol & PROTO_FAMILY_HTTP) && + (type & CLIENTWRITE_HEADER) && !(type & CLIENTWRITE_STATUS) ) { + unsigned char htype = (unsigned char) + (type & CLIENTWRITE_CONNECT ? CURLH_CONNECT : + (type & CLIENTWRITE_1XX ? CURLH_1XX : + (type & CLIENTWRITE_TRAILER ? CURLH_TRAILER : + CURLH_HEADER))); + CURLcode result = Curl_headers_push(data, optr, htype); + if(result) + return result; + } +#endif + + if(writeheader) { + size_t wrote; + + Curl_set_in_callback(data, true); + wrote = writeheader(optr, 1, olen, data->set.writeheader); + Curl_set_in_callback(data, false); + + if(CURL_WRITEFUNC_PAUSE == wrote) + return pausewrite(data, type, FALSE, optr, olen); + if(wrote != olen) { + failf(data, "Failed writing header"); + return CURLE_WRITE_ERROR; + } + } + + return CURLE_OK; +} + + +/* Curl_client_write() sends data to the write callback(s) + + The bit pattern defines to what "streams" to write to. Body and/or header. + The defines are in sendf.h of course. + + If CURL_DO_LINEEND_CONV is enabled, data is converted IN PLACE to the + local character encoding. This is a problem and should be changed in + the future to leave the original data alone. + */ +CURLcode Curl_client_write(struct Curl_easy *data, + int type, char *buf, size_t blen) +{ + CURLcode result; + +#if !defined(CURL_DISABLE_FTP) && defined(CURL_DO_LINEEND_CONV) + /* FTP data may need conversion. */ + if((type & CLIENTWRITE_BODY) && + (data->conn->handler->protocol & PROTO_FAMILY_FTP) && + data->conn->proto.ftpc.transfertype == 'A') { + /* convert end-of-line markers */ + blen = convert_lineends(data, buf, blen); + } +#endif + /* it is one of those, at least */ + DEBUGASSERT(type & (CLIENTWRITE_BODY|CLIENTWRITE_HEADER|CLIENTWRITE_INFO)); + /* BODY is only BODY */ + DEBUGASSERT(!(type & CLIENTWRITE_BODY) || (type == CLIENTWRITE_BODY)); + /* INFO is only INFO */ + DEBUGASSERT(!(type & CLIENTWRITE_INFO) || (type == CLIENTWRITE_INFO)); + + if(!data->req.writer_stack) { + result = do_init_stack(data); + if(result) + return result; + DEBUGASSERT(data->req.writer_stack); + } + + return Curl_cwriter_write(data, data->req.writer_stack, type, buf, blen); +} + +CURLcode Curl_client_unpause(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + + if(data->state.tempcount) { + /* there are buffers for sending that can be delivered as the receive + pausing is lifted! */ + unsigned int i; + unsigned int count = data->state.tempcount; + struct tempbuf writebuf[3]; /* there can only be three */ + + /* copy the structs to allow for immediate re-pausing */ + for(i = 0; i < data->state.tempcount; i++) { + writebuf[i] = data->state.tempwrite[i]; + Curl_dyn_init(&data->state.tempwrite[i].b, DYN_PAUSE_BUFFER); + } + data->state.tempcount = 0; + + for(i = 0; i < count; i++) { + /* even if one function returns error, this loops through and frees + all buffers */ + if(!result) + result = chop_write(data, writebuf[i].type, + !writebuf[i].paused_body, + Curl_dyn_ptr(&writebuf[i].b), + Curl_dyn_len(&writebuf[i].b)); + Curl_dyn_free(&writebuf[i].b); + } + } + return result; +} + +void Curl_client_cleanup(struct Curl_easy *data) +{ + struct Curl_cwriter *writer = data->req.writer_stack; + size_t i; + + while(writer) { + data->req.writer_stack = writer->next; + writer->cwt->do_close(data, writer); + free(writer); + writer = data->req.writer_stack; + } + + for(i = 0; i < data->state.tempcount; i++) { + Curl_dyn_free(&data->state.tempwrite[i].b); + } + data->state.tempcount = 0; + data->req.bytecount = 0; + data->req.headerline = 0; +} + +/* Write data using an unencoding writer stack. "nbytes" is not + allowed to be 0. */ +CURLcode Curl_cwriter_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + if(!nbytes) + return CURLE_OK; + 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) +{ + (void)data; + (void)writer; + return CURLE_OK; +} + +CURLcode Curl_cwriter_def_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); +} + +void Curl_cwriter_def_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + (void) data; + (void) writer; +} + +/* Real client writer to installed callbacks. */ +static CURLcode cw_client_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + (void)writer; + if(!nbytes) + return CURLE_OK; + return chop_write(data, type, FALSE, (char *)buf, nbytes); +} + +static const struct Curl_cwtype cw_client = { + "client", + NULL, + Curl_cwriter_def_init, + cw_client_write, + Curl_cwriter_def_close, + sizeof(struct Curl_cwriter) +}; + +static size_t get_max_body_write_len(struct Curl_easy *data, curl_off_t limit) +{ + if(limit != -1) { + /* How much more are we allowed to write? */ + curl_off_t remain_diff; + remain_diff = limit - data->req.bytecount; + if(remain_diff < 0) { + /* already written too much! */ + return 0; + } +#if SIZEOF_CURL_OFF_T > SIZEOF_SIZE_T + else if(remain_diff > SSIZE_T_MAX) { + return SIZE_T_MAX; + } +#endif + else { + return (size_t)remain_diff; + } + } + return SIZE_T_MAX; +} + +/* Download client writer in phase CURL_CW_PROTOCOL that + * sees the "real" download body data. */ +static CURLcode cw_download_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + CURLcode result; + size_t nwrite, excess_len = 0; + const char *excess_data = NULL; + + if(!(type & CLIENTWRITE_BODY)) { + if((type & CLIENTWRITE_CONNECT) && data->set.suppress_connect_headers) + return CURLE_OK; + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + } + + nwrite = nbytes; + if(-1 != data->req.maxdownload) { + size_t wmax = get_max_body_write_len(data, data->req.maxdownload); + if(nwrite > wmax) { + excess_len = nbytes - wmax; + nwrite = wmax; + excess_data = buf + nwrite; + } + + if(nwrite == wmax) { + data->req.download_done = TRUE; + } + } + + if(data->set.max_filesize) { + size_t wmax = get_max_body_write_len(data, data->set.max_filesize); + if(nwrite > wmax) { + nwrite = wmax; + } + } + + data->req.bytecount += nwrite; + ++data->req.bodywrites; + if(!data->req.ignorebody && nwrite) { + result = Curl_cwriter_write(data, writer->next, type, buf, nwrite); + if(result) + return result; + } + result = Curl_pgrsSetDownloadCounter(data, data->req.bytecount); + if(result) + return result; + + if(excess_len) { + if(data->conn->handler->readwrite) { + /* RTSP hack moved from transfer loop to here */ + bool readmore = FALSE; /* indicates data is incomplete, need more */ + size_t consumed = 0; + result = data->conn->handler->readwrite(data, data->conn, + excess_data, excess_len, + &consumed, &readmore); + if(result) + return result; + DEBUGASSERT(consumed <= excess_len); + excess_len -= consumed; + if(readmore) { + data->req.download_done = FALSE; + data->req.keepon |= KEEP_RECV; /* we're not done reading */ + } + } + if(excess_len && !data->req.ignorebody) { + infof(data, + "Excess found writing body:" + " excess = %zu" + ", size = %" CURL_FORMAT_CURL_OFF_T + ", maxdownload = %" CURL_FORMAT_CURL_OFF_T + ", bytecount = %" CURL_FORMAT_CURL_OFF_T, + excess_len, data->req.size, data->req.maxdownload, + data->req.bytecount); + connclose(data->conn, "excess found in a read"); + } + } + else if(nwrite < nbytes) { + failf(data, "Exceeded the maximum allowed file size " + "(%" CURL_FORMAT_CURL_OFF_T ") with %" + CURL_FORMAT_CURL_OFF_T " bytes", + data->set.max_filesize, data->req.bytecount); + return CURLE_FILESIZE_EXCEEDED; + } + + return CURLE_OK; +} + +static const struct Curl_cwtype cw_download = { + "download", + NULL, + Curl_cwriter_def_init, + cw_download_write, + Curl_cwriter_def_close, + sizeof(struct Curl_cwriter) +}; + +/* RAW client writer in phase CURL_CW_RAW that + * enabled tracing of raw data. */ +static CURLcode cw_raw_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + if(type & CLIENTWRITE_BODY && data->set.verbose && !data->req.ignorebody) { + Curl_debug(data, CURLINFO_DATA_IN, (char *)buf, nbytes); + } + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); +} + +static const struct Curl_cwtype cw_raw = { + "raw", + NULL, + Curl_cwriter_def_init, + cw_raw_write, + Curl_cwriter_def_close, + sizeof(struct Curl_cwriter) +}; + +/* Create an unencoding writer stage using the given handler. */ +CURLcode Curl_cwriter_create(struct Curl_cwriter **pwriter, + struct Curl_easy *data, + const struct Curl_cwtype *cwt, + Curl_cwriter_phase phase) +{ + struct Curl_cwriter *writer; + CURLcode result = CURLE_OUT_OF_MEMORY; + + DEBUGASSERT(cwt->cwriter_size >= sizeof(struct Curl_cwriter)); + writer = (struct Curl_cwriter *) calloc(1, cwt->cwriter_size); + if(!writer) + goto out; + + writer->cwt = cwt; + writer->phase = phase; + result = cwt->do_init(data, writer); + +out: + *pwriter = result? NULL : writer; + if(result) + free(writer); + return result; +} + +void Curl_cwriter_free(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + if(writer) { + writer->cwt->do_close(data, writer); + free(writer); + } +} + +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) { + if(w->phase == phase) + ++n; + } + return n; +} + +static CURLcode do_init_stack(struct Curl_easy *data) +{ + struct Curl_cwriter *writer; + CURLcode result; + + DEBUGASSERT(!data->req.writer_stack); + result = Curl_cwriter_create(&data->req.writer_stack, + data, &cw_client, CURL_CW_CLIENT); + if(result) + return result; + + result = Curl_cwriter_create(&writer, data, &cw_download, CURL_CW_PROTOCOL); + if(result) + return result; + result = Curl_cwriter_add(data, writer); + if(result) { + Curl_cwriter_free(data, writer); + } + + result = Curl_cwriter_create(&writer, data, &cw_raw, CURL_CW_RAW); + if(result) + return result; + result = Curl_cwriter_add(data, writer); + if(result) { + Curl_cwriter_free(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; + + if(!*anchor) { + result = do_init_stack(data); + if(result) + 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; + return CURLE_OK; +} + + +/* + * Internal read-from-socket function. This is meant to deal with plain + * sockets, SSL sockets and kerberos sockets. + * + * Returns a regular CURLcode value. + */ +CURLcode Curl_read(struct Curl_easy *data, /* transfer */ + curl_socket_t sockfd, /* read from this socket */ + char *buf, /* store read data here */ + size_t sizerequested, /* max amount to read */ + ssize_t *n) /* amount bytes read */ +{ + CURLcode result = CURLE_RECV_ERROR; + ssize_t nread = 0; + size_t bytesfromsocket = 0; + char *buffertofill = NULL; + struct connectdata *conn = data->conn; + + /* Set 'num' to 0 or 1, depending on which socket that has been sent here. + If it is the second socket, we set num to 1. Otherwise to 0. This lets + us use the correct ssl handle. */ + int num = (sockfd == conn->sock[SECONDARYSOCKET]); + + *n = 0; /* reset amount to zero */ + + bytesfromsocket = CURLMIN(sizerequested, (size_t)data->set.buffer_size); + buffertofill = buf; + + nread = conn->recv[num](data, num, buffertofill, bytesfromsocket, &result); + if(nread < 0) + goto out; + + *n += nread; + result = CURLE_OK; +out: + return result; +} diff --git a/Utilities/cmcurl/lib/sendf.h b/Utilities/cmcurl/lib/sendf.h new file mode 100644 index 0000000..a70189f --- /dev/null +++ b/Utilities/cmcurl/lib/sendf.h @@ -0,0 +1,189 @@ +#ifndef HEADER_CURL_SENDF_H +#define HEADER_CURL_SENDF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_trc.h" + +/** + * Type of data that is being written to the client (application) + * - data written can be either BODY or META data + * - META data is either INFO or HEADER + * - INFO is meta information, e.g. not BODY, that cannot be interpreted + * as headers of a response. Example FTP/IMAP pingpong answers. + * - HEADER can have additional bits set (more than one) + * - STATUS special "header", e.g. response status line in HTTP + * - CONNECT header was received during proxying the connection + * - 1XX header is part of an intermediate response, e.g. HTTP 1xx code + * - TRAILER header is trailing response data, e.g. HTTP trailers + * BODY, INFO and HEADER should not be mixed, as this would lead to + * confusion on how to interpret/format/convert the data. + */ +#define CLIENTWRITE_BODY (1<<0) /* non-meta information, BODY */ +#define CLIENTWRITE_INFO (1<<1) /* meta information, not a HEADER */ +#define CLIENTWRITE_HEADER (1<<2) /* meta information, HEADER */ +#define CLIENTWRITE_STATUS (1<<3) /* a special status HEADER */ +#define CLIENTWRITE_CONNECT (1<<4) /* a CONNECT related HEADER */ +#define CLIENTWRITE_1XX (1<<5) /* a 1xx response related HEADER */ +#define CLIENTWRITE_TRAILER (1<<6) /* a trailer HEADER */ + +/** + * Write `len` bytes at `prt` to the client. `type` indicates what + * kind of data is being written. + */ +CURLcode Curl_client_write(struct Curl_easy *data, int type, char *ptr, + size_t len) WARN_UNUSED_RESULT; + +/** + * For a paused transfer, there might be buffered data held back. + * Attempt to flush this data to the client. This *may* trigger + * another pause of the transfer. + */ +CURLcode Curl_client_unpause(struct Curl_easy *data); + +/** + * Free all resources related to client writing. + */ +void Curl_client_cleanup(struct Curl_easy *data); + +/** + * Client Writers - a chain passing transfer BODY data to the client. + * Main application: HTTP and related protocols + * Other uses: monitoring of download progress + * + * Writers in the chain are order by their `phase`. First come all + * writers in CURL_CW_RAW, followed by any in CURL_CW_TRANSFER_DECODE, + * followed by any in CURL_CW_PROTOCOL, etc. + * + * When adding a writer, it is inserted as first in its phase. This means + * the order of adding writers of the same phase matters, but writers for + * different phases may be added in any order. + * + * Writers which do modify the BODY data written are expected to be of + * phases TRANSFER_DECODE or CONTENT_DECODE. The other phases are intended + * for monitoring writers. Which do *not* modify the data but gather + * statistics or update progress reporting. + */ + +/* Phase a writer operates at. */ +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_CONTENT_DECODE, /* remove content-encodings */ + CURL_CW_CLIENT /* data written to client */ +} Curl_cwriter_phase; + +/* Client Writer Type, provides the implementation */ +struct Curl_cwtype { + const char *name; /* writer name. */ + const char *alias; /* writer name alias, maybe NULL. */ + 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); + void (*do_close)(struct Curl_easy *data, + struct Curl_cwriter *writer); + size_t cwriter_size; /* sizeof() allocated struct Curl_cwriter */ +}; + +/* Client writer instance */ +struct Curl_cwriter { + const struct Curl_cwtype *cwt; /* type implementation */ + struct Curl_cwriter *next; /* Downstream writer. */ + Curl_cwriter_phase phase; /* phase at which it operates */ +}; + +/** + * Create a new cwriter instance with given type and phase. Is not + * inserted into the writer chain by this call. + * Invokes `writer->do_init()`. + */ +CURLcode Curl_cwriter_create(struct Curl_cwriter **pwriter, + struct Curl_easy *data, + const struct Curl_cwtype *ce_handler, + Curl_cwriter_phase phase); + +/** + * Free a cwriter instance. + * Invokes `writer->do_close()`. + */ +void Curl_cwriter_free(struct Curl_easy *data, + struct Curl_cwriter *writer); + +/** + * Count the number of writers installed of the given phase. + */ +size_t Curl_cwriter_count(struct Curl_easy *data, Curl_cwriter_phase phase); + +/** + * Adds a writer to the transfer's writer chain. + * The writers `phase` determines where in the chain it is inserted. + */ +CURLcode Curl_cwriter_add(struct Curl_easy *data, + struct Curl_cwriter *writer); + +/** + * 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); + +/** + * Default implementations for do_init, do_write, do_close that + * do nothing and pass the data through. + */ +CURLcode Curl_cwriter_def_init(struct Curl_easy *data, + struct Curl_cwriter *writer); +CURLcode Curl_cwriter_def_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); +void Curl_cwriter_def_close(struct Curl_easy *data, + struct Curl_cwriter *writer); + + +/* internal read-function, does plain socket, SSL and krb4 */ +CURLcode Curl_read(struct Curl_easy *data, curl_socket_t sockfd, + char *buf, size_t buffersize, + ssize_t *n); + +/* internal write-function, does plain socket, SSL, SCP, SFTP and krb4 */ +CURLcode Curl_write(struct Curl_easy *data, + curl_socket_t sockfd, + const void *mem, size_t len, + ssize_t *written); + +/* internal write-function, using sockindex for connection destination */ +CURLcode Curl_nwrite(struct Curl_easy *data, + int sockindex, + const void *buf, + size_t blen, + ssize_t *pnwritten); + +#endif /* HEADER_CURL_SENDF_H */ diff --git a/Utilities/cmcurl/lib/setopt.c b/Utilities/cmcurl/lib/setopt.c new file mode 100644 index 0000000..a08140c --- /dev/null +++ b/Utilities/cmcurl/lib/setopt.c @@ -0,0 +1,3167 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <limits.h> + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#ifdef HAVE_LINUX_TCP_H +#include <linux/tcp.h> +#elif defined(HAVE_NETINET_TCP_H) +#include <netinet/tcp.h> +#endif + +#include "urldata.h" +#include "url.h" +#include "progress.h" +#include "content_encoding.h" +#include "strcase.h" +#include "share.h" +#include "vtls/vtls.h" +#include "warnless.h" +#include "sendf.h" +#include "http2.h" +#include "setopt.h" +#include "multiif.h" +#include "altsvc.h" +#include "hsts.h" +#include "tftp.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +CURLcode Curl_setstropt(char **charp, 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. */ + + Curl_safefree(*charp); + + if(s) { + if(strlen(s) > CURL_MAX_INPUT_LENGTH) + return CURLE_BAD_FUNCTION_ARGUMENT; + + *charp = strdup(s); + if(!*charp) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +CURLcode Curl_setblobopt(struct curl_blob **blobp, + const struct curl_blob *blob) +{ + /* free the previous storage at `blobp' and replace by a dynamic storage + copy of blob. If CURL_BLOB_COPY is set, the data is copied. */ + + Curl_safefree(*blobp); + + if(blob) { + struct curl_blob *nblob; + if(blob->len > CURL_MAX_INPUT_LENGTH) + return CURLE_BAD_FUNCTION_ARGUMENT; + nblob = (struct curl_blob *) + malloc(sizeof(struct curl_blob) + + ((blob->flags & CURL_BLOB_COPY) ? blob->len : 0)); + if(!nblob) + return CURLE_OUT_OF_MEMORY; + *nblob = *blob; + if(blob->flags & CURL_BLOB_COPY) { + /* put the data after the blob struct in memory */ + nblob->data = (char *)nblob + sizeof(struct curl_blob); + memcpy(nblob->data, blob->data, blob->len); + } + + *blobp = nblob; + return CURLE_OK; + } + + return CURLE_OK; +} + +static CURLcode setstropt_userpwd(char *option, char **userp, char **passwdp) +{ + CURLcode result = CURLE_OK; + char *user = NULL; + char *passwd = NULL; + + /* Parse the login details if specified. It not then we treat NULL as a hint + to clear the existing data */ + if(option) { + size_t len = strlen(option); + if(len > CURL_MAX_INPUT_LENGTH) + return CURLE_BAD_FUNCTION_ARGUMENT; + + result = Curl_parse_login_details(option, len, + (userp ? &user : NULL), + (passwdp ? &passwd : NULL), + NULL); + } + + if(!result) { + /* Store the username part of option if required */ + if(userp) { + if(!user && option && option[0] == ':') { + /* Allocate an empty string instead of returning NULL as user name */ + user = strdup(""); + if(!user) + result = CURLE_OUT_OF_MEMORY; + } + + Curl_safefree(*userp); + *userp = user; + } + + /* Store the password part of option if required */ + if(passwdp) { + Curl_safefree(*passwdp); + *passwdp = passwd; + } + } + + return result; +} + +#define C_SSLVERSION_VALUE(x) (x & 0xffff) +#define C_SSLVERSION_MAX_VALUE(x) (x & 0xffff0000) + +static CURLcode protocol2num(const char *str, curl_prot_t *val) +{ + if(!str) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(curl_strequal(str, "all")) { + *val = ~(curl_prot_t) 0; + return CURLE_OK; + } + + *val = 0; + + do { + const char *token = str; + size_t tlen; + + str = strchr(str, ','); + tlen = str? (size_t) (str - token): strlen(token); + if(tlen) { + const struct Curl_handler *h = Curl_getn_scheme_handler(token, tlen); + + if(!h) + return CURLE_UNSUPPORTED_PROTOCOL; + + *val |= h->protocol; + } + } while(str && str++); + + if(!*val) + /* no protocol listed */ + return CURLE_BAD_FUNCTION_ARGUMENT; + return CURLE_OK; +} + +/* + * Do not make Curl_vsetopt() static: it is called from + * packages/OS400/ccsidcurl.c. + */ +CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) +{ + char *argptr; + CURLcode result = CURLE_OK; + long arg; + unsigned long uarg; + curl_off_t bigsize; + + switch(option) { + case CURLOPT_DNS_CACHE_TIMEOUT: + arg = va_arg(param, long); + if(arg < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; + + data->set.dns_cache_timeout = (int)arg; + break; + case CURLOPT_CA_CACHE_TIMEOUT: + arg = va_arg(param, long); + if(arg < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; + + data->set.general_ssl.ca_cache_timeout = (int)arg; + break; + case CURLOPT_DNS_USE_GLOBAL_CACHE: + /* deprecated */ + break; + case CURLOPT_SSL_CIPHER_LIST: + /* set a list of cipher we want to use in the SSL connection */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST], + va_arg(param, char *)); + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSL_CIPHER_LIST: + /* set a list of cipher we want to use in the SSL connection for proxy */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST_PROXY], + va_arg(param, char *)); + break; +#endif + case CURLOPT_TLS13_CIPHERS: + if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) { + /* set preferred list of TLS 1.3 cipher suites */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER13_LIST], + va_arg(param, char *)); + } + else + return CURLE_NOT_BUILT_IN; + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_TLS13_CIPHERS: + if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) { + /* set preferred list of TLS 1.3 cipher suites for proxy */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER13_LIST_PROXY], + va_arg(param, char *)); + } + else + return CURLE_NOT_BUILT_IN; + break; +#endif + case CURLOPT_RANDOM_FILE: + break; + case CURLOPT_EGDSOCKET: + break; + case CURLOPT_MAXCONNECTS: + /* + * Set the absolute number of maximum simultaneous alive connection that + * libcurl is allowed to have. + */ + uarg = va_arg(param, unsigned long); + if(uarg > UINT_MAX) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.maxconnects = (unsigned int)uarg; + break; + case CURLOPT_FORBID_REUSE: + /* + * When this transfer is done, it must not be left to be reused by a + * subsequent transfer but shall be closed immediately. + */ + data->set.reuse_forbid = (0 != va_arg(param, long)); + break; + case CURLOPT_FRESH_CONNECT: + /* + * This transfer shall not use a previously cached connection but + * should be made with a fresh new connect! + */ + data->set.reuse_fresh = (0 != va_arg(param, long)); + break; + case CURLOPT_VERBOSE: + /* + * Verbose means infof() calls that give a lot of information about + * the connection and transfer procedures as well as internal choices. + */ + data->set.verbose = (0 != va_arg(param, long)); + break; + case CURLOPT_HEADER: + /* + * Set to include the header in the general data output stream. + */ + data->set.include_header = (0 != va_arg(param, long)); + break; + case CURLOPT_NOPROGRESS: + /* + * Shut off the internal supported progress meter + */ + data->set.hide_progress = (0 != va_arg(param, long)); + if(data->set.hide_progress) + data->progress.flags |= PGRS_HIDE; + else + data->progress.flags &= ~PGRS_HIDE; + break; + case CURLOPT_NOBODY: + /* + * Do not include the body part in the output data stream. + */ + data->set.opt_no_body = (0 != va_arg(param, long)); +#ifndef CURL_DISABLE_HTTP + if(data->set.opt_no_body) + /* in HTTP lingo, no body means using the HEAD request... */ + data->set.method = HTTPREQ_HEAD; + else if(data->set.method == HTTPREQ_HEAD) + data->set.method = HTTPREQ_GET; +#endif + break; + case CURLOPT_FAILONERROR: + /* + * Don't output the >=400 error code HTML-page, but instead only + * return error. + */ + data->set.http_fail_on_error = (0 != va_arg(param, long)); + break; + case CURLOPT_KEEP_SENDING_ON_ERROR: + data->set.http_keep_sending_on_error = (0 != va_arg(param, long)); + break; + case CURLOPT_UPLOAD: + case CURLOPT_PUT: + /* + * We want to sent data to the remote host. If this is HTTP, that equals + * using the PUT request. + */ + arg = va_arg(param, long); + if(arg) { + /* If this is HTTP, PUT is what's needed to "upload" */ + data->set.method = HTTPREQ_PUT; + data->set.opt_no_body = FALSE; /* this is implied */ + } + else + /* In HTTP, the opposite of upload is GET (unless NOBODY is true as + then this can be changed to HEAD later on) */ + data->set.method = HTTPREQ_GET; + break; + case CURLOPT_REQUEST_TARGET: + result = Curl_setstropt(&data->set.str[STRING_TARGET], + va_arg(param, char *)); + break; + case CURLOPT_FILETIME: + /* + * Try to get the file time of the remote document. The time will + * later (possibly) become available using curl_easy_getinfo(). + */ + data->set.get_filetime = (0 != va_arg(param, long)); + break; + case CURLOPT_SERVER_RESPONSE_TIMEOUT: + /* + * Option that specifies how quickly a server response must be obtained + * before it is considered failure. For pingpong protocols. + */ + arg = va_arg(param, long); + if((arg >= 0) && (arg <= (INT_MAX/1000))) + data->set.server_response_timeout = (unsigned int)arg * 1000; + else + return CURLE_BAD_FUNCTION_ARGUMENT; + break; +#ifndef CURL_DISABLE_TFTP + case CURLOPT_TFTP_NO_OPTIONS: + /* + * Option that prevents libcurl from sending TFTP option requests to the + * server. + */ + data->set.tftp_no_options = va_arg(param, long) != 0; + break; + case CURLOPT_TFTP_BLKSIZE: + /* + * TFTP option that specifies the block size to use for data transmission. + */ + arg = va_arg(param, long); + if(arg > TFTP_BLKSIZE_MAX || arg < TFTP_BLKSIZE_MIN) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.tftp_blksize = arg; + break; +#endif +#ifndef CURL_DISABLE_NETRC + case CURLOPT_NETRC: + /* + * Parse the $HOME/.netrc file + */ + arg = va_arg(param, long); + if((arg < CURL_NETRC_IGNORED) || (arg >= CURL_NETRC_LAST)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.use_netrc = (unsigned char)arg; + break; + case CURLOPT_NETRC_FILE: + /* + * Use this file instead of the $HOME/.netrc file + */ + result = Curl_setstropt(&data->set.str[STRING_NETRC_FILE], + va_arg(param, char *)); + break; +#endif + case CURLOPT_TRANSFERTEXT: + /* + * This option was previously named 'FTPASCII'. Renamed to work with + * more protocols than merely FTP. + * + * Transfer using ASCII (instead of BINARY). + */ + data->set.prefer_ascii = (0 != va_arg(param, long)); + break; + case CURLOPT_TIMECONDITION: + /* + * Set HTTP time condition. This must be one of the defines in the + * curl/curl.h header file. + */ + arg = va_arg(param, long); + if((arg < CURL_TIMECOND_NONE) || (arg >= CURL_TIMECOND_LAST)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.timecondition = (unsigned char)(curl_TimeCond)arg; + break; + case CURLOPT_TIMEVALUE: + /* + * This is the value to compare with the remote document with the + * method set with CURLOPT_TIMECONDITION + */ + data->set.timevalue = (time_t)va_arg(param, long); + break; + + case CURLOPT_TIMEVALUE_LARGE: + /* + * This is the value to compare with the remote document with the + * method set with CURLOPT_TIMECONDITION + */ + data->set.timevalue = (time_t)va_arg(param, curl_off_t); + break; + + case CURLOPT_SSLVERSION: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLVERSION: +#endif + /* + * Set explicit SSL version to try to connect with, as some SSL + * implementations are lame. + */ +#ifdef USE_SSL + { + long version, version_max; + struct ssl_primary_config *primary = &data->set.ssl.primary; +#ifndef CURL_DISABLE_PROXY + if(option != CURLOPT_SSLVERSION) + primary = &data->set.proxy_ssl.primary; +#endif + + arg = va_arg(param, long); + + version = C_SSLVERSION_VALUE(arg); + version_max = C_SSLVERSION_MAX_VALUE(arg); + + if(version < CURL_SSLVERSION_DEFAULT || + version == CURL_SSLVERSION_SSLv2 || + version == CURL_SSLVERSION_SSLv3 || + version >= CURL_SSLVERSION_LAST || + version_max < CURL_SSLVERSION_MAX_NONE || + version_max >= CURL_SSLVERSION_MAX_LAST) + return CURLE_BAD_FUNCTION_ARGUMENT; + + primary->version = (unsigned char)version; + primary->version_max = (unsigned int)version_max; + } +#else + result = CURLE_NOT_BUILT_IN; +#endif + break; + + /* MQTT "borrows" some of the HTTP options */ +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MQTT) + case CURLOPT_COPYPOSTFIELDS: + /* + * A string with POST data. Makes curl HTTP POST. Even if it is NULL. + * If needed, CURLOPT_POSTFIELDSIZE must have been set prior to + * CURLOPT_COPYPOSTFIELDS and not altered later. + */ + argptr = va_arg(param, char *); + + if(!argptr || data->set.postfieldsize == -1) + result = Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], argptr); + else { + /* + * Check that requested length does not overflow the size_t type. + */ + + if((data->set.postfieldsize < 0) || + ((sizeof(curl_off_t) != sizeof(size_t)) && + (data->set.postfieldsize > (curl_off_t)((size_t)-1)))) + result = CURLE_OUT_OF_MEMORY; + else { + char *p; + + (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + + /* 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. + */ + p = malloc((size_t)(data->set.postfieldsize? + data->set.postfieldsize:1)); + + if(!p) + result = CURLE_OUT_OF_MEMORY; + else { + if(data->set.postfieldsize) + memcpy(p, argptr, (size_t)data->set.postfieldsize); + + data->set.str[STRING_COPYPOSTFIELDS] = p; + } + } + } + + data->set.postfields = data->set.str[STRING_COPYPOSTFIELDS]; + data->set.method = HTTPREQ_POST; + break; + + case CURLOPT_POSTFIELDS: + /* + * Like above, but use static data instead of copying it. + */ + data->set.postfields = va_arg(param, void *); + /* Release old copied data. */ + (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + data->set.method = HTTPREQ_POST; + break; + + case CURLOPT_POSTFIELDSIZE: + /* + * The size of the POSTFIELD data to prevent libcurl to do strlen() to + * figure it out. Enables binary posts. + */ + bigsize = va_arg(param, long); + if(bigsize < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(data->set.postfieldsize < bigsize && + data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { + /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ + (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + data->set.postfields = NULL; + } + + data->set.postfieldsize = bigsize; + break; + + case CURLOPT_POSTFIELDSIZE_LARGE: + /* + * The size of the POSTFIELD data to prevent libcurl to do strlen() to + * figure it out. Enables binary posts. + */ + bigsize = va_arg(param, curl_off_t); + if(bigsize < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(data->set.postfieldsize < bigsize && + data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { + /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ + (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + data->set.postfields = NULL; + } + + data->set.postfieldsize = bigsize; + break; +#endif +#ifndef CURL_DISABLE_HTTP + case CURLOPT_AUTOREFERER: + /* + * Switch on automatic referer that gets set if curl follows locations. + */ + data->set.http_auto_referer = (0 != va_arg(param, long)); + break; + + case CURLOPT_ACCEPT_ENCODING: + /* + * String to use at the value of Accept-Encoding header. + * + * 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 don't send an Accept-Encoding header + * and ignore an received Content-Encoding header. + * + */ + argptr = va_arg(param, char *); + if(argptr && !*argptr) { + char all[256]; + Curl_all_content_encodings(all, sizeof(all)); + result = Curl_setstropt(&data->set.str[STRING_ENCODING], all); + } + else + result = Curl_setstropt(&data->set.str[STRING_ENCODING], argptr); + break; + + case CURLOPT_TRANSFER_ENCODING: + data->set.http_transfer_encoding = (0 != va_arg(param, long)); + break; + + case CURLOPT_FOLLOWLOCATION: + /* + * Follow Location: header hints on an HTTP-server. + */ + data->set.http_follow_location = (0 != va_arg(param, long)); + break; + + case CURLOPT_UNRESTRICTED_AUTH: + /* + * Send authentication (user+password) when following locations, even when + * hostname changed. + */ + data->set.allow_auth_to_other_hosts = (0 != va_arg(param, long)); + break; + + case CURLOPT_MAXREDIRS: + /* + * The maximum amount of hops you allow curl to follow Location: + * headers. This should mostly be used to detect never-ending loops. + */ + arg = va_arg(param, long); + if(arg < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.maxredirs = arg; + break; + + case CURLOPT_POSTREDIR: + /* + * Set the behavior of POST when redirecting + * CURL_REDIR_GET_ALL - POST is changed to GET after 301 and 302 + * CURL_REDIR_POST_301 - POST is kept as POST after 301 + * CURL_REDIR_POST_302 - POST is kept as POST after 302 + * CURL_REDIR_POST_303 - POST is kept as POST after 303 + * CURL_REDIR_POST_ALL - POST is kept as POST after 301, 302 and 303 + * other - POST is kept as POST after 301 and 302 + */ + arg = va_arg(param, long); + if(arg < CURL_REDIR_GET_ALL) + /* no return error on too high numbers since the bitmask could be + extended in a future */ + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.keep_post = arg & CURL_REDIR_POST_ALL; + break; + + case CURLOPT_POST: + /* Does this option serve a purpose anymore? Yes it does, when + CURLOPT_POSTFIELDS isn't used and the POST data is read off the + callback! */ + if(va_arg(param, long)) { + data->set.method = HTTPREQ_POST; + data->set.opt_no_body = FALSE; /* this is implied */ + } + else + data->set.method = HTTPREQ_GET; + break; + +#ifndef CURL_DISABLE_FORM_API + case CURLOPT_HTTPPOST: + /* + * Set to make us do HTTP POST. Legacy API-style. + */ + data->set.httppost = va_arg(param, struct curl_httppost *); + data->set.method = HTTPREQ_POST_FORM; + data->set.opt_no_body = FALSE; /* this is implied */ + Curl_mime_cleanpart(data->state.formp); + Curl_safefree(data->state.formp); + break; +#endif + +#if !defined(CURL_DISABLE_AWS) + case CURLOPT_AWS_SIGV4: + /* + * String that is merged to some authentication + * parameters are used by the algorithm. + */ + result = Curl_setstropt(&data->set.str[STRING_AWS_SIGV4], + va_arg(param, char *)); + /* + * Basic been set by default it need to be unset here + */ + if(data->set.str[STRING_AWS_SIGV4]) + data->set.httpauth = CURLAUTH_AWS_SIGV4; + break; +#endif + + case CURLOPT_REFERER: + /* + * String to set in the HTTP Referer: field. + */ + if(data->state.referer_alloc) { + Curl_safefree(data->state.referer); + data->state.referer_alloc = FALSE; + } + result = Curl_setstropt(&data->set.str[STRING_SET_REFERER], + va_arg(param, char *)); + data->state.referer = data->set.str[STRING_SET_REFERER]; + break; + + case CURLOPT_USERAGENT: + /* + * String to use in the HTTP User-Agent field + */ + result = Curl_setstropt(&data->set.str[STRING_USERAGENT], + va_arg(param, char *)); + break; + +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXYHEADER: + /* + * Set a list with proxy headers to use (or replace internals with) + * + * Since CURLOPT_HTTPHEADER was the only way to set HTTP headers for a + * long time we remain doing it this way until CURLOPT_PROXYHEADER is + * used. As soon as this option has been used, if set to anything but + * NULL, custom headers for proxies are only picked from this list. + * + * Set this option to NULL to restore the previous behavior. + */ + data->set.proxyheaders = va_arg(param, struct curl_slist *); + break; +#endif + case CURLOPT_HEADEROPT: + /* + * Set header option. + */ + arg = va_arg(param, long); + data->set.sep_headers = !!(arg & CURLHEADER_SEPARATE); + break; + +#if !defined(CURL_DISABLE_COOKIES) + case CURLOPT_COOKIE: + /* + * Cookie string to send to the remote server in the request. + */ + result = Curl_setstropt(&data->set.str[STRING_COOKIE], + va_arg(param, char *)); + break; + + case CURLOPT_COOKIEFILE: + /* + * Set cookie file to read and parse. Can be used multiple times. + */ + argptr = (char *)va_arg(param, void *); + if(argptr) { + struct curl_slist *cl; + /* general protection against mistakes and abuse */ + if(strlen(argptr) > CURL_MAX_INPUT_LENGTH) + return CURLE_BAD_FUNCTION_ARGUMENT; + /* append the cookie file name to the list of file names, and deal with + them later */ + cl = curl_slist_append(data->state.cookielist, argptr); + if(!cl) { + curl_slist_free_all(data->state.cookielist); + data->state.cookielist = NULL; + return CURLE_OUT_OF_MEMORY; + } + data->state.cookielist = cl; /* store the list for later use */ + } + else { + /* clear the list of cookie files */ + curl_slist_free_all(data->state.cookielist); + data->state.cookielist = NULL; + + if(!data->share || !data->share->cookies) { + /* throw away all existing cookies if this isn't a shared cookie + container */ + Curl_cookie_clearall(data->cookies); + Curl_cookie_cleanup(data->cookies); + } + /* disable the cookie engine */ + data->cookies = NULL; + } + break; + + case CURLOPT_COOKIEJAR: + /* + * Set cookie file name to dump all cookies to when we're done. + */ + { + struct CookieInfo *newcookies; + result = Curl_setstropt(&data->set.str[STRING_COOKIEJAR], + va_arg(param, char *)); + + /* + * Activate the cookie parser. This may or may not already + * have been made. + */ + newcookies = Curl_cookie_init(data, NULL, data->cookies, + data->set.cookiesession); + if(!newcookies) + result = CURLE_OUT_OF_MEMORY; + data->cookies = newcookies; + } + break; + + case CURLOPT_COOKIESESSION: + /* + * Set this option to TRUE to start a new "cookie session". It will + * prevent the forthcoming read-cookies-from-file actions to accept + * cookies that are marked as being session cookies, as they belong to a + * previous session. + */ + data->set.cookiesession = (0 != va_arg(param, long)); + break; + + case CURLOPT_COOKIELIST: + argptr = va_arg(param, char *); + + if(!argptr) + break; + + if(strcasecompare(argptr, "ALL")) { + /* clear all cookies */ + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + Curl_cookie_clearall(data->cookies); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } + else if(strcasecompare(argptr, "SESS")) { + /* clear session cookies */ + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + Curl_cookie_clearsess(data->cookies); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } + else if(strcasecompare(argptr, "FLUSH")) { + /* flush cookies to file, takes care of the locking */ + Curl_flush_cookies(data, FALSE); + } + else if(strcasecompare(argptr, "RELOAD")) { + /* reload cookies from file */ + Curl_cookie_loadfiles(data); + break; + } + else { + if(!data->cookies) + /* if cookie engine was not running, activate it */ + data->cookies = Curl_cookie_init(data, NULL, NULL, TRUE); + + /* general protection against mistakes and abuse */ + if(strlen(argptr) > CURL_MAX_INPUT_LENGTH) + return CURLE_BAD_FUNCTION_ARGUMENT; + argptr = strdup(argptr); + if(!argptr || !data->cookies) { + result = CURLE_OUT_OF_MEMORY; + free(argptr); + } + else { + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + + if(checkprefix("Set-Cookie:", argptr)) + /* HTTP Header format line */ + Curl_cookie_add(data, data->cookies, TRUE, FALSE, argptr + 11, NULL, + NULL, TRUE); + + else + /* Netscape format line */ + Curl_cookie_add(data, data->cookies, FALSE, FALSE, argptr, NULL, + NULL, TRUE); + + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + free(argptr); + } + } + + break; +#endif /* !CURL_DISABLE_COOKIES */ + + case CURLOPT_HTTPGET: + /* + * Set to force us do HTTP GET + */ + if(va_arg(param, long)) { + data->set.method = HTTPREQ_GET; + data->set.opt_no_body = FALSE; /* this is implied */ + } + break; + + case CURLOPT_HTTP_VERSION: + /* + * This sets a requested HTTP version to be used. The value is one of + * the listed enums in curl/curl.h. + */ + arg = va_arg(param, long); + switch(arg) { + case CURL_HTTP_VERSION_NONE: +#ifdef USE_HTTP2 + /* TODO: this seems an undesirable quirk to force a behaviour on + * lower implementations that they should recognize independently? */ + arg = CURL_HTTP_VERSION_2TLS; +#endif + /* accepted */ + break; + case CURL_HTTP_VERSION_1_0: + case CURL_HTTP_VERSION_1_1: + /* accepted */ + break; +#ifdef USE_HTTP2 + case CURL_HTTP_VERSION_2_0: + case CURL_HTTP_VERSION_2TLS: + case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: + /* accepted */ + break; +#endif +#ifdef ENABLE_QUIC + case CURL_HTTP_VERSION_3: + case CURL_HTTP_VERSION_3ONLY: + /* accepted */ + break; +#endif + default: + /* not accepted */ + if(arg < CURL_HTTP_VERSION_NONE) + return CURLE_BAD_FUNCTION_ARGUMENT; + return CURLE_UNSUPPORTED_PROTOCOL; + } + data->set.httpwant = (unsigned char)arg; + break; + + case CURLOPT_EXPECT_100_TIMEOUT_MS: + /* + * Time to wait for a response to an HTTP request containing an + * Expect: 100-continue header before sending the data anyway. + */ + arg = va_arg(param, long); + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.expect_100_timeout = arg; + break; + + case CURLOPT_HTTP09_ALLOWED: + arg = va_arg(param, unsigned long); + if(arg > 1L) + return CURLE_BAD_FUNCTION_ARGUMENT; +#ifdef USE_HYPER + /* Hyper does not support HTTP/0.9 */ + if(arg) + return CURLE_BAD_FUNCTION_ARGUMENT; +#else + data->set.http09_allowed = !!arg; +#endif + break; + + case CURLOPT_HTTP200ALIASES: + /* + * Set a list of aliases for HTTP 200 in response header + */ + data->set.http200aliases = va_arg(param, struct curl_slist *); + break; +#endif /* CURL_DISABLE_HTTP */ + +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_IMAP) +# if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MIME) + case CURLOPT_HTTPHEADER: + /* + * Set a list with HTTP headers to use (or replace internals with) + */ + data->set.headers = va_arg(param, struct curl_slist *); + break; +# endif + +# ifndef CURL_DISABLE_MIME + case CURLOPT_MIMEPOST: + /* + * Set to make us do MIME POST + */ + result = Curl_mime_set_subparts(&data->set.mimepost, + va_arg(param, curl_mime *), FALSE); + if(!result) { + data->set.method = HTTPREQ_POST_MIME; + data->set.opt_no_body = FALSE; /* this is implied */ +#ifndef CURL_DISABLE_FORM_API + Curl_mime_cleanpart(data->state.formp); + Curl_safefree(data->state.formp); +#endif + } + break; + + case CURLOPT_MIME_OPTIONS: + arg = va_arg(param, long); + data->set.mime_formescape = !!(arg & CURLMIMEOPT_FORMESCAPE); + break; +# endif +#endif + + case CURLOPT_HTTPAUTH: + /* + * Set HTTP Authentication type BITMASK. + */ + { + int bitcheck; + bool authbits; + unsigned long auth = va_arg(param, unsigned long); + + if(auth == CURLAUTH_NONE) { + data->set.httpauth = auth; + break; + } + + /* the DIGEST_IE bit is only used to set a special marker, for all the + rest we need to handle it as normal DIGEST */ + data->state.authhost.iestyle = !!(auth & CURLAUTH_DIGEST_IE); + + if(auth & CURLAUTH_DIGEST_IE) { + auth |= CURLAUTH_DIGEST; /* set standard digest bit */ + auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ + } + + /* switch off bits we can't support */ +#ifndef USE_NTLM + auth &= ~CURLAUTH_NTLM; /* no NTLM support */ + auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ +#elif !defined(NTLM_WB_ENABLED) + auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ +#endif +#ifndef USE_SPNEGO + auth &= ~CURLAUTH_NEGOTIATE; /* no Negotiate (SPNEGO) auth without + GSS-API or SSPI */ +#endif + + /* check if any auth bit lower than CURLAUTH_ONLY is still set */ + bitcheck = 0; + authbits = FALSE; + while(bitcheck < 31) { + if(auth & (1UL << bitcheck++)) { + authbits = TRUE; + break; + } + } + if(!authbits) + return CURLE_NOT_BUILT_IN; /* no supported types left! */ + + data->set.httpauth = auth; + } + break; + + case CURLOPT_CUSTOMREQUEST: + /* + * Set a custom string to use as request + */ + result = Curl_setstropt(&data->set.str[STRING_CUSTOMREQUEST], + va_arg(param, char *)); + + /* we don't set + data->set.method = HTTPREQ_CUSTOM; + here, we continue as if we were using the already set type + and this just changes the actual request keyword */ + break; + +#ifndef CURL_DISABLE_PROXY + case CURLOPT_HTTPPROXYTUNNEL: + /* + * Tunnel operations through the proxy instead of normal proxy use + */ + data->set.tunnel_thru_httpproxy = (0 != va_arg(param, long)); + break; + + case CURLOPT_PROXYPORT: + /* + * Explicitly set HTTP proxy port number. + */ + arg = va_arg(param, long); + if((arg < 0) || (arg > 65535)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.proxyport = (unsigned short)arg; + break; + + case CURLOPT_PROXYAUTH: + /* + * Set HTTP Authentication type BITMASK. + */ + { + int bitcheck; + bool authbits; + unsigned long auth = va_arg(param, unsigned long); + + if(auth == CURLAUTH_NONE) { + data->set.proxyauth = auth; + break; + } + + /* the DIGEST_IE bit is only used to set a special marker, for all the + rest we need to handle it as normal DIGEST */ + data->state.authproxy.iestyle = !!(auth & CURLAUTH_DIGEST_IE); + + if(auth & CURLAUTH_DIGEST_IE) { + auth |= CURLAUTH_DIGEST; /* set standard digest bit */ + auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ + } + /* switch off bits we can't support */ +#ifndef USE_NTLM + auth &= ~CURLAUTH_NTLM; /* no NTLM support */ + auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ +#elif !defined(NTLM_WB_ENABLED) + auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ +#endif +#ifndef USE_SPNEGO + auth &= ~CURLAUTH_NEGOTIATE; /* no Negotiate (SPNEGO) auth without + GSS-API or SSPI */ +#endif + + /* check if any auth bit lower than CURLAUTH_ONLY is still set */ + bitcheck = 0; + authbits = FALSE; + while(bitcheck < 31) { + if(auth & (1UL << bitcheck++)) { + authbits = TRUE; + break; + } + } + if(!authbits) + return CURLE_NOT_BUILT_IN; /* no supported types left! */ + + data->set.proxyauth = auth; + } + break; + + case CURLOPT_PROXY: + /* + * Set proxy server:port to use as proxy. + * + * If the proxy is set to "" (and CURLOPT_SOCKS_PROXY is set to "" or NULL) + * we explicitly say that we don't want to use a proxy + * (even though there might be environment variables saying so). + * + * Setting it to NULL, means no proxy but allows the environment variables + * to decide for us (if CURLOPT_SOCKS_PROXY setting it to NULL). + */ + result = Curl_setstropt(&data->set.str[STRING_PROXY], + va_arg(param, char *)); + break; + + case CURLOPT_PRE_PROXY: + /* + * Set proxy server:port to use as SOCKS proxy. + * + * If the proxy is set to "" or NULL we explicitly say that we don't want + * to use the socks proxy. + */ + result = Curl_setstropt(&data->set.str[STRING_PRE_PROXY], + va_arg(param, char *)); + break; + + case CURLOPT_PROXYTYPE: + /* + * Set proxy type. + */ + arg = va_arg(param, long); + if((arg < CURLPROXY_HTTP) || (arg > CURLPROXY_SOCKS5_HOSTNAME)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.proxytype = (unsigned char)(curl_proxytype)arg; + break; + + case CURLOPT_PROXY_TRANSFER_MODE: + /* + * set transfer mode (;type=<a|i>) when doing FTP via an HTTP proxy + */ + switch(va_arg(param, long)) { + case 0: + data->set.proxy_transfer_mode = FALSE; + break; + case 1: + data->set.proxy_transfer_mode = TRUE; + break; + default: + /* reserve other values for future use */ + result = CURLE_BAD_FUNCTION_ARGUMENT; + break; + } + break; + + case CURLOPT_SOCKS5_AUTH: + data->set.socks5auth = (unsigned char)va_arg(param, unsigned long); + if(data->set.socks5auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) + result = CURLE_NOT_BUILT_IN; + break; +#endif /* CURL_DISABLE_PROXY */ + +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + case CURLOPT_SOCKS5_GSSAPI_NEC: + /* + * Set flag for NEC SOCK5 support + */ + data->set.socks5_gssapi_nec = (0 != va_arg(param, long)); + break; +#endif +#ifndef CURL_DISABLE_PROXY + case CURLOPT_SOCKS5_GSSAPI_SERVICE: + case CURLOPT_PROXY_SERVICE_NAME: + /* + * Set proxy authentication service name for Kerberos 5 and SPNEGO + */ + result = Curl_setstropt(&data->set.str[STRING_PROXY_SERVICE_NAME], + va_arg(param, char *)); + break; +#endif + case CURLOPT_SERVICE_NAME: + /* + * Set authentication service name for DIGEST-MD5, Kerberos 5 and SPNEGO + */ + result = Curl_setstropt(&data->set.str[STRING_SERVICE_NAME], + va_arg(param, char *)); + break; + + case CURLOPT_HEADERDATA: + /* + * Custom pointer to pass the header write callback function + */ + data->set.writeheader = (void *)va_arg(param, void *); + break; + case CURLOPT_ERRORBUFFER: + /* + * Error buffer provided by the caller to get the human readable + * error string in. + */ + data->set.errorbuffer = va_arg(param, char *); + break; + case CURLOPT_WRITEDATA: + /* + * FILE pointer to write to. Or possibly + * used as argument to the write callback. + */ + data->set.out = va_arg(param, void *); + break; + +#ifdef CURL_LIST_ONLY_PROTOCOL + case CURLOPT_DIRLISTONLY: + /* + * An option that changes the command to one that asks for a list only, no + * file info details. Used for FTP, POP3 and SFTP. + */ + data->set.list_only = (0 != va_arg(param, long)); + break; +#endif + case CURLOPT_APPEND: + /* + * We want to upload and append to an existing file. Used for FTP and + * SFTP. + */ + data->set.remote_append = (0 != va_arg(param, long)); + break; + +#ifndef CURL_DISABLE_FTP + case CURLOPT_FTP_FILEMETHOD: + /* + * How do access files over FTP. + */ + arg = va_arg(param, long); + if((arg < CURLFTPMETHOD_DEFAULT) || (arg >= CURLFTPMETHOD_LAST)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.ftp_filemethod = (unsigned char)arg; + break; + case CURLOPT_FTPPORT: + /* + * Use FTP PORT, this also specifies which IP address to use + */ + result = Curl_setstropt(&data->set.str[STRING_FTPPORT], + va_arg(param, char *)); + data->set.ftp_use_port = !!(data->set.str[STRING_FTPPORT]); + break; + + case CURLOPT_FTP_USE_EPRT: + data->set.ftp_use_eprt = (0 != va_arg(param, long)); + break; + + case CURLOPT_FTP_USE_EPSV: + data->set.ftp_use_epsv = (0 != va_arg(param, long)); + break; + + case CURLOPT_FTP_USE_PRET: + data->set.ftp_use_pret = (0 != va_arg(param, long)); + break; + + case CURLOPT_FTP_SSL_CCC: + arg = va_arg(param, long); + if((arg < CURLFTPSSL_CCC_NONE) || (arg >= CURLFTPSSL_CCC_LAST)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.ftp_ccc = (unsigned char)arg; + break; + + case CURLOPT_FTP_SKIP_PASV_IP: + /* + * Enable or disable FTP_SKIP_PASV_IP, which will disable/enable the + * bypass of the IP address in PASV responses. + */ + data->set.ftp_skip_ip = (0 != va_arg(param, long)); + break; + + case CURLOPT_FTP_ACCOUNT: + result = Curl_setstropt(&data->set.str[STRING_FTP_ACCOUNT], + va_arg(param, char *)); + break; + + case CURLOPT_FTP_ALTERNATIVE_TO_USER: + result = Curl_setstropt(&data->set.str[STRING_FTP_ALTERNATIVE_TO_USER], + va_arg(param, char *)); + break; + + case CURLOPT_FTPSSLAUTH: + /* + * Set a specific auth for FTP-SSL transfers. + */ + arg = va_arg(param, long); + if((arg < CURLFTPAUTH_DEFAULT) || (arg >= CURLFTPAUTH_LAST)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.ftpsslauth = (unsigned char)(curl_ftpauth)arg; + break; + case CURLOPT_KRBLEVEL: + /* + * A string that defines the kerberos security level. + */ + result = Curl_setstropt(&data->set.str[STRING_KRB_LEVEL], + va_arg(param, char *)); + data->set.krb = !!(data->set.str[STRING_KRB_LEVEL]); + break; +#endif +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) + case CURLOPT_FTP_CREATE_MISSING_DIRS: + /* + * An FTP/SFTP option that modifies an upload to create missing + * directories on the server. + */ + arg = va_arg(param, long); + /* reserve other values for future use */ + if((arg < CURLFTP_CREATE_DIR_NONE) || + (arg > CURLFTP_CREATE_DIR_RETRY)) + result = CURLE_BAD_FUNCTION_ARGUMENT; + else + data->set.ftp_create_missing_dirs = (unsigned char)arg; + break; + + case CURLOPT_POSTQUOTE: + /* + * List of RAW FTP commands to use after a transfer + */ + data->set.postquote = va_arg(param, struct curl_slist *); + break; + case CURLOPT_PREQUOTE: + /* + * List of RAW FTP commands to use prior to RETR (Wesley Laxton) + */ + data->set.prequote = va_arg(param, struct curl_slist *); + break; + case CURLOPT_QUOTE: + /* + * List of RAW FTP commands to use before a transfer + */ + data->set.quote = va_arg(param, struct curl_slist *); + break; +#endif + case CURLOPT_READDATA: + /* + * FILE pointer to read the file to be uploaded from. Or possibly + * used as argument to the read callback. + */ + data->set.in_set = va_arg(param, void *); + break; + case CURLOPT_INFILESIZE: + /* + * If known, this should inform curl about the file size of the + * to-be-uploaded file. + */ + arg = va_arg(param, long); + if(arg < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.filesize = arg; + break; + case CURLOPT_INFILESIZE_LARGE: + /* + * If known, this should inform curl about the file size of the + * to-be-uploaded file. + */ + bigsize = va_arg(param, curl_off_t); + if(bigsize < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.filesize = bigsize; + break; + case CURLOPT_LOW_SPEED_LIMIT: + /* + * The low speed limit that if transfers are below this for + * CURLOPT_LOW_SPEED_TIME, the transfer is aborted. + */ + arg = va_arg(param, long); + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.low_speed_limit = arg; + break; + case CURLOPT_MAX_SEND_SPEED_LARGE: + /* + * When transfer uploads are faster then CURLOPT_MAX_SEND_SPEED_LARGE + * bytes per second the transfer is throttled.. + */ + bigsize = va_arg(param, curl_off_t); + if(bigsize < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.max_send_speed = bigsize; + break; + case CURLOPT_MAX_RECV_SPEED_LARGE: + /* + * When receiving data faster than CURLOPT_MAX_RECV_SPEED_LARGE bytes per + * second the transfer is throttled.. + */ + bigsize = va_arg(param, curl_off_t); + if(bigsize < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.max_recv_speed = bigsize; + break; + case CURLOPT_LOW_SPEED_TIME: + /* + * The low speed time that if transfers are below the set + * CURLOPT_LOW_SPEED_LIMIT during this time, the transfer is aborted. + */ + arg = va_arg(param, long); + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.low_speed_time = arg; + break; + case CURLOPT_CURLU: + /* + * pass CURLU to set URL + */ + data->set.uh = va_arg(param, CURLU *); + break; + case CURLOPT_URL: + /* + * The URL to fetch. + */ + if(data->state.url_alloc) { + /* the already set URL is allocated, free it first! */ + Curl_safefree(data->state.url); + data->state.url_alloc = FALSE; + } + result = Curl_setstropt(&data->set.str[STRING_SET_URL], + va_arg(param, char *)); + data->state.url = data->set.str[STRING_SET_URL]; + break; + case CURLOPT_PORT: + /* + * The port number to use when getting the URL. 0 disables it. + */ + arg = va_arg(param, long); + if((arg < 0) || (arg > 65535)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.use_port = (unsigned short)arg; + break; + case CURLOPT_TIMEOUT: + /* + * The maximum time you allow curl to use for a single transfer + * operation. + */ + arg = va_arg(param, long); + if((arg >= 0) && (arg <= (INT_MAX/1000))) + data->set.timeout = (unsigned int)arg * 1000; + else + return CURLE_BAD_FUNCTION_ARGUMENT; + break; + + case CURLOPT_TIMEOUT_MS: + uarg = va_arg(param, unsigned long); + if(uarg > UINT_MAX) + uarg = UINT_MAX; + data->set.timeout = (unsigned int)uarg; + break; + + case CURLOPT_CONNECTTIMEOUT: + /* + * The maximum time you allow curl to use to connect. + */ + arg = va_arg(param, long); + if((arg >= 0) && (arg <= (INT_MAX/1000))) + data->set.connecttimeout = (unsigned int)arg * 1000; + else + return CURLE_BAD_FUNCTION_ARGUMENT; + break; + + case CURLOPT_CONNECTTIMEOUT_MS: + uarg = va_arg(param, unsigned long); + if(uarg > UINT_MAX) + uarg = UINT_MAX; + data->set.connecttimeout = (unsigned int)uarg; + break; + +#ifndef CURL_DISABLE_FTP + case CURLOPT_ACCEPTTIMEOUT_MS: + /* + * The maximum time for curl to wait for FTP server connect + */ + uarg = va_arg(param, unsigned long); + if(uarg > UINT_MAX) + uarg = UINT_MAX; + data->set.accepttimeout = (unsigned int)uarg; + break; +#endif + + case CURLOPT_USERPWD: + /* + * user:password to use in the operation + */ + result = setstropt_userpwd(va_arg(param, char *), + &data->set.str[STRING_USERNAME], + &data->set.str[STRING_PASSWORD]); + break; + + case CURLOPT_USERNAME: + /* + * authentication user name to use in the operation + */ + result = Curl_setstropt(&data->set.str[STRING_USERNAME], + va_arg(param, char *)); + break; + case CURLOPT_PASSWORD: + /* + * authentication password to use in the operation + */ + result = Curl_setstropt(&data->set.str[STRING_PASSWORD], + va_arg(param, char *)); + break; + + case CURLOPT_LOGIN_OPTIONS: + /* + * authentication options to use in the operation + */ + result = Curl_setstropt(&data->set.str[STRING_OPTIONS], + va_arg(param, char *)); + break; + + case CURLOPT_XOAUTH2_BEARER: + /* + * OAuth 2.0 bearer token to use in the operation + */ + result = Curl_setstropt(&data->set.str[STRING_BEARER], + va_arg(param, char *)); + break; + + case CURLOPT_RESOLVE: + /* + * List of HOST:PORT:[addresses] strings to populate the DNS cache with + * Entries added this way will remain in the cache until explicitly + * removed or the handle is cleaned up. + * + * Prefix the HOST with plus sign (+) to have the entry expire just like + * automatically added entries. + * + * Prefix the HOST with dash (-) to _remove_ the entry from the cache. + * + * This API can remove any entry from the DNS cache, but only entries + * that aren't actually in use right now will be pruned immediately. + */ + data->set.resolve = va_arg(param, struct curl_slist *); + data->state.resolve = data->set.resolve; + break; + case CURLOPT_PROGRESSFUNCTION: + /* + * Progress callback function + */ + data->set.fprogress = va_arg(param, curl_progress_callback); + if(data->set.fprogress) + data->progress.callback = TRUE; /* no longer internal */ + else + data->progress.callback = FALSE; /* NULL enforces internal */ + break; + + case CURLOPT_XFERINFOFUNCTION: + /* + * Transfer info callback function + */ + data->set.fxferinfo = va_arg(param, curl_xferinfo_callback); + if(data->set.fxferinfo) + data->progress.callback = TRUE; /* no longer internal */ + else + data->progress.callback = FALSE; /* NULL enforces internal */ + + break; + + case CURLOPT_PROGRESSDATA: + /* + * Custom client data to pass to the progress callback + */ + data->set.progress_client = va_arg(param, void *); + break; + +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXYUSERPWD: + /* + * user:password needed to use the proxy + */ + result = setstropt_userpwd(va_arg(param, char *), + &data->set.str[STRING_PROXYUSERNAME], + &data->set.str[STRING_PROXYPASSWORD]); + break; + case CURLOPT_PROXYUSERNAME: + /* + * authentication user name to use in the operation + */ + result = Curl_setstropt(&data->set.str[STRING_PROXYUSERNAME], + va_arg(param, char *)); + break; + case CURLOPT_PROXYPASSWORD: + /* + * authentication password to use in the operation + */ + result = Curl_setstropt(&data->set.str[STRING_PROXYPASSWORD], + va_arg(param, char *)); + break; + case CURLOPT_NOPROXY: + /* + * proxy exception list + */ + result = Curl_setstropt(&data->set.str[STRING_NOPROXY], + va_arg(param, char *)); + break; +#endif + + case CURLOPT_RANGE: + /* + * What range of the file you want to transfer + */ + result = Curl_setstropt(&data->set.str[STRING_SET_RANGE], + va_arg(param, char *)); + break; + case CURLOPT_RESUME_FROM: + /* + * Resume transfer at the given file position + */ + arg = va_arg(param, long); + if(arg < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.set_resume_from = arg; + break; + case CURLOPT_RESUME_FROM_LARGE: + /* + * Resume transfer at the given file position + */ + bigsize = va_arg(param, curl_off_t); + if(bigsize < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.set_resume_from = bigsize; + break; + case CURLOPT_DEBUGFUNCTION: + /* + * stderr write callback. + */ + data->set.fdebug = va_arg(param, curl_debug_callback); + /* + * if the callback provided is NULL, it'll use the default callback + */ + break; + case CURLOPT_DEBUGDATA: + /* + * Set to a void * that should receive all error writes. This + * defaults to CURLOPT_STDERR for normal operations. + */ + data->set.debugdata = va_arg(param, void *); + break; + case CURLOPT_STDERR: + /* + * Set to a FILE * that should receive all error writes. This + * defaults to stderr for normal operations. + */ + data->set.err = va_arg(param, FILE *); + if(!data->set.err) + data->set.err = stderr; + break; + case CURLOPT_HEADERFUNCTION: + /* + * Set header write callback + */ + data->set.fwrite_header = va_arg(param, curl_write_callback); + break; + case CURLOPT_WRITEFUNCTION: + /* + * Set data write callback + */ + data->set.fwrite_func = va_arg(param, curl_write_callback); + if(!data->set.fwrite_func) + /* When set to NULL, reset to our internal default function */ + data->set.fwrite_func = (curl_write_callback)fwrite; + break; + case CURLOPT_READFUNCTION: + /* + * Read data callback + */ + data->set.fread_func_set = va_arg(param, curl_read_callback); + if(!data->set.fread_func_set) { + data->set.is_fread_set = 0; + /* When set to NULL, reset to our internal default function */ + data->set.fread_func_set = (curl_read_callback)fread; + } + else + data->set.is_fread_set = 1; + break; + case CURLOPT_SEEKFUNCTION: + /* + * Seek callback. Might be NULL. + */ + data->set.seek_func = va_arg(param, curl_seek_callback); + break; + case CURLOPT_SEEKDATA: + /* + * Seek control callback. Might be NULL. + */ + data->set.seek_client = va_arg(param, void *); + break; + case CURLOPT_IOCTLFUNCTION: + /* + * I/O control callback. Might be NULL. + */ + data->set.ioctl_func = va_arg(param, curl_ioctl_callback); + break; + case CURLOPT_IOCTLDATA: + /* + * I/O control data pointer. Might be NULL. + */ + data->set.ioctl_client = va_arg(param, void *); + break; + case CURLOPT_SSLCERT: + /* + * String that holds file name of the SSL certificate to use + */ + result = Curl_setstropt(&data->set.str[STRING_CERT], + va_arg(param, char *)); + break; + case CURLOPT_SSLCERT_BLOB: + /* + * Blob that holds file content of the SSL certificate to use + */ + result = Curl_setblobopt(&data->set.blobs[BLOB_CERT], + va_arg(param, struct curl_blob *)); + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLCERT: + /* + * String that holds file name of the SSL certificate to use for proxy + */ + result = Curl_setstropt(&data->set.str[STRING_CERT_PROXY], + va_arg(param, char *)); + break; + case CURLOPT_PROXY_SSLCERT_BLOB: + /* + * Blob that holds file content of the SSL certificate to use for proxy + */ + result = Curl_setblobopt(&data->set.blobs[BLOB_CERT_PROXY], + va_arg(param, struct curl_blob *)); + break; +#endif + case CURLOPT_SSLCERTTYPE: + /* + * String that holds file type of the SSL certificate to use + */ + result = Curl_setstropt(&data->set.str[STRING_CERT_TYPE], + va_arg(param, char *)); + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLCERTTYPE: + /* + * String that holds file type of the SSL certificate to use for proxy + */ + result = Curl_setstropt(&data->set.str[STRING_CERT_TYPE_PROXY], + va_arg(param, char *)); + break; +#endif + case CURLOPT_SSLKEY: + /* + * String that holds file name of the SSL key to use + */ + result = Curl_setstropt(&data->set.str[STRING_KEY], + va_arg(param, char *)); + break; + case CURLOPT_SSLKEY_BLOB: + /* + * Blob that holds file content of the SSL key to use + */ + result = Curl_setblobopt(&data->set.blobs[BLOB_KEY], + va_arg(param, struct curl_blob *)); + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLKEY: + /* + * String that holds file name of the SSL key to use for proxy + */ + result = Curl_setstropt(&data->set.str[STRING_KEY_PROXY], + va_arg(param, char *)); + break; + case CURLOPT_PROXY_SSLKEY_BLOB: + /* + * Blob that holds file content of the SSL key to use for proxy + */ + result = Curl_setblobopt(&data->set.blobs[BLOB_KEY_PROXY], + va_arg(param, struct curl_blob *)); + break; +#endif + case CURLOPT_SSLKEYTYPE: + /* + * String that holds file type of the SSL key to use + */ + result = Curl_setstropt(&data->set.str[STRING_KEY_TYPE], + va_arg(param, char *)); + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLKEYTYPE: + /* + * String that holds file type of the SSL key to use for proxy + */ + result = Curl_setstropt(&data->set.str[STRING_KEY_TYPE_PROXY], + va_arg(param, char *)); + break; +#endif + case CURLOPT_KEYPASSWD: + /* + * String that holds the SSL or SSH private key password. + */ + result = Curl_setstropt(&data->set.str[STRING_KEY_PASSWD], + va_arg(param, char *)); + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_KEYPASSWD: + /* + * String that holds the SSL private key password for proxy. + */ + result = Curl_setstropt(&data->set.str[STRING_KEY_PASSWD_PROXY], + va_arg(param, char *)); + break; +#endif + case CURLOPT_SSLENGINE: + /* + * String that holds the SSL crypto engine. + */ + argptr = va_arg(param, char *); + if(argptr && argptr[0]) { + result = Curl_setstropt(&data->set.str[STRING_SSL_ENGINE], argptr); + if(!result) { + result = Curl_ssl_set_engine(data, argptr); + } + } + break; + + case CURLOPT_SSLENGINE_DEFAULT: + /* + * flag to set engine as default. + */ + Curl_setstropt(&data->set.str[STRING_SSL_ENGINE], NULL); + result = Curl_ssl_set_engine_default(data); + break; + case CURLOPT_CRLF: + /* + * Kludgy option to enable CRLF conversions. Subject for removal. + */ + data->set.crlf = (0 != va_arg(param, long)); + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_HAPROXYPROTOCOL: + /* + * Set to send the HAProxy Proxy Protocol header + */ + data->set.haproxyprotocol = (0 != va_arg(param, long)); + break; + case CURLOPT_HAPROXY_CLIENT_IP: + /* + * Set the client IP to send through HAProxy PROXY protocol + */ + result = Curl_setstropt(&data->set.str[STRING_HAPROXY_CLIENT_IP], + va_arg(param, char *)); + /* We enable implicitly the HAProxy protocol if we use this flag. */ + data->set.haproxyprotocol = TRUE; + break; +#endif + 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. + */ + result = Curl_setstropt(&data->set.str[STRING_DEVICE], + va_arg(param, char *)); + break; +#ifndef CURL_DISABLE_BINDLOCAL + case CURLOPT_LOCALPORT: + /* + * Set what local port to bind the socket to when performing an operation. + */ + arg = va_arg(param, long); + if((arg < 0) || (arg > 65535)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.localport = curlx_sltous(arg); + break; + case CURLOPT_LOCALPORTRANGE: + /* + * Set number of local ports to try, starting with CURLOPT_LOCALPORT. + */ + arg = va_arg(param, long); + if((arg < 0) || (arg > 65535)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.localportrange = curlx_sltous(arg); + break; +#endif + case CURLOPT_GSSAPI_DELEGATION: + /* + * GSS-API credential delegation bitmask + */ + uarg = va_arg(param, unsigned long); + data->set.gssapi_delegation = (unsigned char)uarg& + (CURLGSSAPI_DELEGATION_POLICY_FLAG|CURLGSSAPI_DELEGATION_FLAG); + break; + case CURLOPT_SSL_VERIFYPEER: + /* + * Enable peer SSL verifying. + */ + data->set.ssl.primary.verifypeer = (0 != va_arg(param, long)); + + /* Update the current connection ssl_config. */ + Curl_ssl_conn_config_update(data, FALSE); + break; +#ifndef CURL_DISABLE_DOH + case CURLOPT_DOH_SSL_VERIFYPEER: + /* + * Enable peer SSL verifying for DoH. + */ + data->set.doh_verifypeer = (0 != va_arg(param, long)); + break; +#endif +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSL_VERIFYPEER: + /* + * Enable peer SSL verifying for proxy. + */ + data->set.proxy_ssl.primary.verifypeer = + (0 != va_arg(param, long))?TRUE:FALSE; + + /* Update the current connection proxy_ssl_config. */ + Curl_ssl_conn_config_update(data, TRUE); + break; +#endif + case CURLOPT_SSL_VERIFYHOST: + /* + * Enable verification of the host name in the peer certificate + */ + arg = va_arg(param, long); + + /* Obviously people are not reading documentation and too many thought + this argument took a boolean when it wasn't and misused it. + Treat 1 and 2 the same */ + data->set.ssl.primary.verifyhost = !!(arg & 3); + + /* Update the current connection ssl_config. */ + Curl_ssl_conn_config_update(data, FALSE); + break; +#ifndef CURL_DISABLE_DOH + case CURLOPT_DOH_SSL_VERIFYHOST: + /* + * Enable verification of the host name in the peer certificate for DoH + */ + arg = va_arg(param, long); + + /* Treat both 1 and 2 as TRUE */ + data->set.doh_verifyhost = !!(arg & 3); + break; +#endif +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSL_VERIFYHOST: + /* + * Enable verification of the host name in the peer certificate for proxy + */ + arg = va_arg(param, long); + + /* Treat both 1 and 2 as TRUE */ + data->set.proxy_ssl.primary.verifyhost = (bool)((arg & 3)?TRUE:FALSE); + /* Update the current connection proxy_ssl_config. */ + Curl_ssl_conn_config_update(data, TRUE); + break; +#endif + case CURLOPT_SSL_VERIFYSTATUS: + /* + * Enable certificate status verifying. + */ + if(!Curl_ssl_cert_status_request()) { + result = CURLE_NOT_BUILT_IN; + break; + } + + data->set.ssl.primary.verifystatus = (0 != va_arg(param, long)); + + /* Update the current connection ssl_config. */ + Curl_ssl_conn_config_update(data, FALSE); + break; +#ifndef CURL_DISABLE_DOH + case CURLOPT_DOH_SSL_VERIFYSTATUS: + /* + * Enable certificate status verifying for DoH. + */ + if(!Curl_ssl_cert_status_request()) { + result = CURLE_NOT_BUILT_IN; + break; + } + + data->set.doh_verifystatus = (0 != va_arg(param, long)); + break; +#endif + case CURLOPT_SSL_CTX_FUNCTION: + /* + * Set a SSL_CTX callback + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) + data->set.ssl.fsslctx = va_arg(param, curl_ssl_ctx_callback); + else +#endif + result = CURLE_NOT_BUILT_IN; + break; + case CURLOPT_SSL_CTX_DATA: + /* + * Set a SSL_CTX callback parameter pointer + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) + data->set.ssl.fsslctxp = va_arg(param, void *); + else +#endif + result = CURLE_NOT_BUILT_IN; + break; + case CURLOPT_SSL_FALSESTART: + /* + * Enable TLS false start. + */ + if(!Curl_ssl_false_start(data)) { + result = CURLE_NOT_BUILT_IN; + break; + } + + data->set.ssl.falsestart = (0 != va_arg(param, long)); + break; + case CURLOPT_CERTINFO: +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CERTINFO)) + data->set.ssl.certinfo = (0 != va_arg(param, long)); + else +#endif + result = CURLE_NOT_BUILT_IN; + break; + case CURLOPT_PINNEDPUBLICKEY: + /* + * Set pinned public key for SSL connection. + * Specify file name of the public key in DER format. + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) + result = Curl_setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY], + va_arg(param, char *)); + else +#endif + result = CURLE_NOT_BUILT_IN; + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_PINNEDPUBLICKEY: + /* + * Set pinned public key for SSL connection. + * Specify file name of the public key in DER format. + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) + result = Curl_setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY], + va_arg(param, char *)); + else +#endif + result = CURLE_NOT_BUILT_IN; + break; +#endif + case CURLOPT_CAINFO: + /* + * Set CA info for SSL connection. Specify file name of the CA certificate + */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE], + va_arg(param, char *)); + break; + case CURLOPT_CAINFO_BLOB: + /* + * Blob that holds CA info for SSL connection. + * Specify entire PEM of the CA certificate + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) { + result = Curl_setblobopt(&data->set.blobs[BLOB_CAINFO], + va_arg(param, struct curl_blob *)); + break; + } + else +#endif + return CURLE_NOT_BUILT_IN; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_CAINFO: + /* + * Set CA info SSL connection for proxy. Specify file name of the + * CA certificate + */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE_PROXY], + va_arg(param, char *)); + break; + case CURLOPT_PROXY_CAINFO_BLOB: + /* + * Blob that holds CA info for SSL connection proxy. + * Specify entire PEM of the CA certificate + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) { + result = Curl_setblobopt(&data->set.blobs[BLOB_CAINFO_PROXY], + va_arg(param, struct curl_blob *)); + break; + } + else +#endif + return CURLE_NOT_BUILT_IN; +#endif + case CURLOPT_CAPATH: + /* + * Set CA path info for SSL connection. Specify directory name of the CA + * certificates which have been prepared using openssl c_rehash utility. + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) + /* This does not work on windows. */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CAPATH], + va_arg(param, char *)); + else +#endif + result = CURLE_NOT_BUILT_IN; + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_CAPATH: + /* + * Set CA path info for SSL connection proxy. Specify directory name of the + * CA certificates which have been prepared using openssl c_rehash utility. + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) + /* This does not work on windows. */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CAPATH_PROXY], + va_arg(param, char *)); + else +#endif + result = CURLE_NOT_BUILT_IN; + break; +#endif + case CURLOPT_CRLFILE: + /* + * Set CRL file info for SSL connection. Specify file name of the CRL + * to check certificates revocation + */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CRLFILE], + va_arg(param, char *)); + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_CRLFILE: + /* + * Set CRL file info for SSL connection for proxy. Specify file name of the + * CRL to check certificates revocation + */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CRLFILE_PROXY], + va_arg(param, char *)); + break; +#endif + case CURLOPT_ISSUERCERT: + /* + * Set Issuer certificate file + * to check certificates issuer + */ + result = Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT], + va_arg(param, char *)); + break; + case CURLOPT_ISSUERCERT_BLOB: + /* + * Blob that holds Issuer certificate to check certificates issuer + */ + result = Curl_setblobopt(&data->set.blobs[BLOB_SSL_ISSUERCERT], + va_arg(param, struct curl_blob *)); + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_ISSUERCERT: + /* + * Set Issuer certificate file + * to check certificates issuer + */ + result = Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT_PROXY], + va_arg(param, char *)); + break; + case CURLOPT_PROXY_ISSUERCERT_BLOB: + /* + * Blob that holds Issuer certificate to check certificates issuer + */ + result = Curl_setblobopt(&data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY], + va_arg(param, struct curl_blob *)); + break; +#endif +#ifndef CURL_DISABLE_TELNET + case CURLOPT_TELNETOPTIONS: + /* + * Set a linked list of telnet options + */ + data->set.telnet_options = va_arg(param, struct curl_slist *); + break; +#endif + case CURLOPT_BUFFERSIZE: + /* + * The application kindly asks for a differently sized receive buffer. + * If it seems reasonable, we'll use it. + */ + if(data->state.buffer) + return CURLE_BAD_FUNCTION_ARGUMENT; + + arg = va_arg(param, long); + + if(arg > READBUFFER_MAX) + arg = READBUFFER_MAX; + else if(arg < 1) + arg = READBUFFER_SIZE; + else if(arg < READBUFFER_MIN) + arg = READBUFFER_MIN; + + data->set.buffer_size = (unsigned int)arg; + break; + + case CURLOPT_UPLOAD_BUFFERSIZE: + /* + * The application kindly asks for a differently sized upload buffer. + * Cap it to sensible. + */ + arg = va_arg(param, long); + + if(arg > UPLOADBUFFER_MAX) + arg = UPLOADBUFFER_MAX; + else if(arg < UPLOADBUFFER_MIN) + arg = UPLOADBUFFER_MIN; + + data->set.upload_buffer_size = (unsigned int)arg; + Curl_safefree(data->state.ulbuf); /* force a realloc next opportunity */ + break; + + case CURLOPT_NOSIGNAL: + /* + * The application asks not to set any signal() or alarm() handlers, + * even when using a timeout. + */ + data->set.no_signal = (0 != va_arg(param, long)); + break; + + case CURLOPT_SHARE: + { + struct Curl_share *set; + set = va_arg(param, struct Curl_share *); + + /* disconnect from old share, if any */ + if(data->share) { + Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); + + if(data->dns.hostcachetype == HCACHE_SHARED) { + data->dns.hostcache = NULL; + data->dns.hostcachetype = HCACHE_NONE; + } + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(data->share->cookies == data->cookies) + data->cookies = NULL; +#endif + +#ifndef CURL_DISABLE_HSTS + if(data->share->hsts == data->hsts) + data->hsts = NULL; +#endif +#ifdef USE_SSL + if(data->share->sslsession == data->state.session) + data->state.session = NULL; +#endif +#ifdef USE_LIBPSL + if(data->psl == &data->share->psl) + data->psl = data->multi? &data->multi->psl: NULL; +#endif + + data->share->dirty--; + + Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); + data->share = NULL; + } + + if(GOOD_SHARE_HANDLE(set)) + /* use new share if it set */ + data->share = set; + if(data->share) { + + Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); + + data->share->dirty++; + + if(data->share->specifier & (1<< CURL_LOCK_DATA_DNS)) { + /* use shared host cache */ + data->dns.hostcache = &data->share->hostcache; + data->dns.hostcachetype = HCACHE_SHARED; + } +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(data->share->cookies) { + /* use shared cookie list, first free own one if any */ + Curl_cookie_cleanup(data->cookies); + /* enable cookies since we now use a share that uses cookies! */ + data->cookies = data->share->cookies; + } +#endif /* CURL_DISABLE_HTTP */ +#ifndef CURL_DISABLE_HSTS + if(data->share->hsts) { + /* first free the private one if any */ + Curl_hsts_cleanup(&data->hsts); + data->hsts = data->share->hsts; + } +#endif /* CURL_DISABLE_HTTP */ +#ifdef USE_SSL + if(data->share->sslsession) { + data->set.general_ssl.max_ssl_sessions = data->share->max_ssl_sessions; + data->state.session = data->share->sslsession; + } +#endif +#ifdef USE_LIBPSL + if(data->share->specifier & (1 << CURL_LOCK_DATA_PSL)) + data->psl = &data->share->psl; +#endif + + Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); + } + /* check for host cache not needed, + * it will be done by curl_easy_perform */ + } + break; + + case CURLOPT_PRIVATE: + /* + * Set private data pointer. + */ + data->set.private_data = va_arg(param, void *); + break; + + case CURLOPT_MAXFILESIZE: + /* + * Set the maximum size of a file to download. + */ + arg = va_arg(param, long); + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.max_filesize = arg; + break; + +#ifdef USE_SSL + case CURLOPT_USE_SSL: + /* + * Make transfers attempt to use SSL/TLS. + */ + arg = va_arg(param, long); + if((arg < CURLUSESSL_NONE) || (arg >= CURLUSESSL_LAST)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.use_ssl = (unsigned char)arg; + break; + + case CURLOPT_SSL_OPTIONS: + arg = va_arg(param, long); + data->set.ssl.primary.ssl_options = (unsigned char)(arg & 0xff); + data->set.ssl.enable_beast = !!(arg & CURLSSLOPT_ALLOW_BEAST); + data->set.ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); + data->set.ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); + data->set.ssl.revoke_best_effort = !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); + data->set.ssl.native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA); + data->set.ssl.auto_client_cert = !!(arg & CURLSSLOPT_AUTO_CLIENT_CERT); + /* If a setting is added here it should also be added in dohprobe() + which sets its own CURLOPT_SSL_OPTIONS based on these settings. */ + break; + +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSL_OPTIONS: + arg = va_arg(param, long); + data->set.proxy_ssl.primary.ssl_options = (unsigned char)(arg & 0xff); + data->set.proxy_ssl.enable_beast = !!(arg & CURLSSLOPT_ALLOW_BEAST); + data->set.proxy_ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); + data->set.proxy_ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); + data->set.proxy_ssl.revoke_best_effort = + !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); + data->set.proxy_ssl.native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA); + data->set.proxy_ssl.auto_client_cert = + !!(arg & CURLSSLOPT_AUTO_CLIENT_CERT); + break; +#endif + + case CURLOPT_SSL_EC_CURVES: + /* + * Set accepted curves in SSL connection setup. + * Specify colon-delimited list of curve algorithm names. + */ + result = Curl_setstropt(&data->set.str[STRING_SSL_EC_CURVES], + va_arg(param, char *)); + break; +#endif + case CURLOPT_IPRESOLVE: + arg = va_arg(param, long); + if((arg < CURL_IPRESOLVE_WHATEVER) || (arg > CURL_IPRESOLVE_V6)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.ipver = (unsigned char) arg; + break; + + case CURLOPT_MAXFILESIZE_LARGE: + /* + * Set the maximum size of a file to download. + */ + bigsize = va_arg(param, curl_off_t); + if(bigsize < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.max_filesize = bigsize; + break; + + case CURLOPT_TCP_NODELAY: + /* + * Enable or disable TCP_NODELAY, which will disable/enable the Nagle + * algorithm + */ + data->set.tcp_nodelay = (0 != va_arg(param, long)); + break; + + case CURLOPT_IGNORE_CONTENT_LENGTH: + data->set.ignorecl = (0 != va_arg(param, long)); + break; + + case CURLOPT_CONNECT_ONLY: + /* + * No data transfer. + * (1) - only do connection + * (2) - do first get request but get no content + */ + arg = va_arg(param, long); + if(arg > 2) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.connect_only = (unsigned char)arg; + break; + + case CURLOPT_SOCKOPTFUNCTION: + /* + * socket callback function: called after socket() but before connect() + */ + data->set.fsockopt = va_arg(param, curl_sockopt_callback); + break; + + case CURLOPT_SOCKOPTDATA: + /* + * socket callback data pointer. Might be NULL. + */ + data->set.sockopt_client = va_arg(param, void *); + break; + + case CURLOPT_OPENSOCKETFUNCTION: + /* + * open/create socket callback function: called instead of socket(), + * before connect() + */ + data->set.fopensocket = va_arg(param, curl_opensocket_callback); + break; + + case CURLOPT_OPENSOCKETDATA: + /* + * socket callback data pointer. Might be NULL. + */ + data->set.opensocket_client = va_arg(param, void *); + break; + + case CURLOPT_CLOSESOCKETFUNCTION: + /* + * close socket callback function: called instead of close() + * when shutting down a connection + */ + data->set.fclosesocket = va_arg(param, curl_closesocket_callback); + break; + + case CURLOPT_RESOLVER_START_FUNCTION: + /* + * resolver start callback function: called before a new resolver request + * is started + */ + data->set.resolver_start = va_arg(param, curl_resolver_start_callback); + break; + + case CURLOPT_RESOLVER_START_DATA: + /* + * resolver start callback data pointer. Might be NULL. + */ + data->set.resolver_start_client = va_arg(param, void *); + break; + + case CURLOPT_CLOSESOCKETDATA: + /* + * socket callback data pointer. Might be NULL. + */ + data->set.closesocket_client = va_arg(param, void *); + break; + + case CURLOPT_SSL_SESSIONID_CACHE: + data->set.ssl.primary.sessionid = (0 != va_arg(param, long)); +#ifndef CURL_DISABLE_PROXY + data->set.proxy_ssl.primary.sessionid = data->set.ssl.primary.sessionid; +#endif + break; + +#ifdef USE_SSH + /* we only include SSH options if explicitly built to support SSH */ + case CURLOPT_SSH_AUTH_TYPES: + data->set.ssh_auth_types = (unsigned int)va_arg(param, long); + break; + + case CURLOPT_SSH_PUBLIC_KEYFILE: + /* + * Use this file instead of the $HOME/.ssh/id_dsa.pub file + */ + result = Curl_setstropt(&data->set.str[STRING_SSH_PUBLIC_KEY], + va_arg(param, char *)); + break; + + case CURLOPT_SSH_PRIVATE_KEYFILE: + /* + * Use this file instead of the $HOME/.ssh/id_dsa file + */ + result = Curl_setstropt(&data->set.str[STRING_SSH_PRIVATE_KEY], + va_arg(param, char *)); + break; + case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: + /* + * Option to allow for the MD5 of the host public key to be checked + * for validation purposes. + */ + result = Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5], + va_arg(param, char *)); + break; + + case CURLOPT_SSH_KNOWNHOSTS: + /* + * Store the file name to read known hosts from. + */ + result = Curl_setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS], + va_arg(param, char *)); + break; +#ifdef USE_LIBSSH2 + case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: + /* + * Option to allow for the SHA256 of the host public key to be checked + * for validation purposes. + */ + result = Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256], + va_arg(param, char *)); + break; + + case CURLOPT_SSH_HOSTKEYFUNCTION: + /* the callback to check the hostkey without the knownhost file */ + data->set.ssh_hostkeyfunc = va_arg(param, curl_sshhostkeycallback); + break; + + case CURLOPT_SSH_HOSTKEYDATA: + /* + * Custom client data to pass to the SSH keyfunc callback + */ + data->set.ssh_hostkeyfunc_userp = va_arg(param, void *); + break; +#endif + + case CURLOPT_SSH_KEYFUNCTION: + /* setting to NULL is fine since the ssh.c functions themselves will + then revert to use the internal default */ + data->set.ssh_keyfunc = va_arg(param, curl_sshkeycallback); + break; + + case CURLOPT_SSH_KEYDATA: + /* + * Custom client data to pass to the SSH keyfunc callback + */ + data->set.ssh_keyfunc_userp = va_arg(param, void *); + break; + + case CURLOPT_SSH_COMPRESSION: + data->set.ssh_compression = (0 != va_arg(param, long))?TRUE:FALSE; + break; +#endif /* USE_SSH */ + + case CURLOPT_HTTP_TRANSFER_DECODING: + /* + * disable libcurl transfer encoding is used + */ +#ifndef USE_HYPER + data->set.http_te_skip = (0 == va_arg(param, long)); + break; +#else + return CURLE_NOT_BUILT_IN; /* hyper doesn't support */ +#endif + + case CURLOPT_HTTP_CONTENT_DECODING: + /* + * raw data passed to the application when content encoding is used + */ + data->set.http_ce_skip = (0 == va_arg(param, long)); + break; + +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) + case CURLOPT_NEW_FILE_PERMS: + /* + * Uses these permissions instead of 0644 + */ + arg = va_arg(param, long); + if((arg < 0) || (arg > 0777)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.new_file_perms = (unsigned int)arg; + break; +#endif +#ifdef USE_SSH + case CURLOPT_NEW_DIRECTORY_PERMS: + /* + * Uses these permissions instead of 0755 + */ + arg = va_arg(param, long); + if((arg < 0) || (arg > 0777)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.new_directory_perms = (unsigned int)arg; + break; +#endif + +#ifdef ENABLE_IPV6 + case CURLOPT_ADDRESS_SCOPE: + /* + * Use this scope id when using IPv6 + * We always get longs when passed plain numericals so we should check + * that the value fits into an unsigned 32 bit integer. + */ + uarg = va_arg(param, unsigned long); +#if SIZEOF_LONG > 4 + if(uarg > UINT_MAX) + return CURLE_BAD_FUNCTION_ARGUMENT; +#endif + data->set.scope_id = (unsigned int)uarg; + break; +#endif + + case CURLOPT_PROTOCOLS: + /* set the bitmask for the protocols that are allowed to be used for the + transfer, which thus helps the app which takes URLs from users or other + external inputs and want to restrict what protocol(s) to deal + with. Defaults to CURLPROTO_ALL. */ + data->set.allowed_protocols = (curl_prot_t)va_arg(param, long); + break; + + case CURLOPT_REDIR_PROTOCOLS: + /* set the bitmask for the protocols that libcurl is allowed to follow to, + as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol needs + to be set in both bitmasks to be allowed to get redirected to. */ + data->set.redir_protocols = (curl_prot_t)va_arg(param, long); + break; + + case CURLOPT_PROTOCOLS_STR: { + curl_prot_t prot; + argptr = va_arg(param, char *); + result = protocol2num(argptr, &prot); + if(result) + return result; + data->set.allowed_protocols = prot; + break; + } + + case CURLOPT_REDIR_PROTOCOLS_STR: { + curl_prot_t prot; + argptr = va_arg(param, char *); + result = protocol2num(argptr, &prot); + if(result) + return result; + data->set.redir_protocols = prot; + break; + } + + case CURLOPT_DEFAULT_PROTOCOL: + /* Set the protocol to use when the URL doesn't include any protocol */ + result = Curl_setstropt(&data->set.str[STRING_DEFAULT_PROTOCOL], + va_arg(param, char *)); + break; +#ifndef CURL_DISABLE_SMTP + case CURLOPT_MAIL_FROM: + /* Set the SMTP mail originator */ + result = Curl_setstropt(&data->set.str[STRING_MAIL_FROM], + va_arg(param, char *)); + break; + + case CURLOPT_MAIL_AUTH: + /* Set the SMTP auth originator */ + result = Curl_setstropt(&data->set.str[STRING_MAIL_AUTH], + va_arg(param, char *)); + break; + + case CURLOPT_MAIL_RCPT: + /* Set the list of mail recipients */ + data->set.mail_rcpt = va_arg(param, struct curl_slist *); + break; + case CURLOPT_MAIL_RCPT_ALLOWFAILS: + /* allow RCPT TO command to fail for some recipients */ + data->set.mail_rcpt_allowfails = (0 != va_arg(param, long)); + break; +#endif + + case CURLOPT_SASL_AUTHZID: + /* Authorization identity (identity to act as) */ + result = Curl_setstropt(&data->set.str[STRING_SASL_AUTHZID], + va_arg(param, char *)); + break; + + case CURLOPT_SASL_IR: + /* Enable/disable SASL initial response */ + data->set.sasl_ir = (0 != va_arg(param, long)); + break; +#ifndef CURL_DISABLE_RTSP + case CURLOPT_RTSP_REQUEST: + { + /* + * Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) + * Would this be better if the RTSPREQ_* were just moved into here? + */ + long in_rtspreq = va_arg(param, long); + Curl_RtspReq rtspreq = RTSPREQ_NONE; + switch(in_rtspreq) { + 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: + rtspreq = RTSPREQ_NONE; + } + + data->set.rtspreq = rtspreq; + break; + } + + + case CURLOPT_RTSP_SESSION_ID: + /* + * Set the RTSP Session ID manually. Useful if the application is + * resuming a previously established RTSP session + */ + result = Curl_setstropt(&data->set.str[STRING_RTSP_SESSION_ID], + va_arg(param, char *)); + break; + + case CURLOPT_RTSP_STREAM_URI: + /* + * Set the Stream URI for the RTSP request. Unless the request is + * for generic server options, the application will need to set this. + */ + result = Curl_setstropt(&data->set.str[STRING_RTSP_STREAM_URI], + va_arg(param, char *)); + break; + + case CURLOPT_RTSP_TRANSPORT: + /* + * The content of the Transport: header for the RTSP request + */ + result = Curl_setstropt(&data->set.str[STRING_RTSP_TRANSPORT], + va_arg(param, char *)); + break; + + case CURLOPT_RTSP_CLIENT_CSEQ: + /* + * Set the CSEQ number to issue for the next RTSP request. Useful if the + * application is resuming a previously broken connection. The CSEQ + * will increment from this new number henceforth. + */ + data->state.rtsp_next_client_CSeq = va_arg(param, long); + break; + + case CURLOPT_RTSP_SERVER_CSEQ: + /* Same as the above, but for server-initiated requests */ + data->state.rtsp_next_server_CSeq = va_arg(param, long); + break; + + case CURLOPT_INTERLEAVEDATA: + data->set.rtp_out = va_arg(param, void *); + break; + case CURLOPT_INTERLEAVEFUNCTION: + /* Set the user defined RTP write function */ + data->set.fwrite_rtp = va_arg(param, curl_write_callback); + break; +#endif +#ifndef CURL_DISABLE_FTP + case CURLOPT_WILDCARDMATCH: + data->set.wildcard_enabled = (0 != va_arg(param, long)); + break; + case CURLOPT_CHUNK_BGN_FUNCTION: + data->set.chunk_bgn = va_arg(param, curl_chunk_bgn_callback); + break; + case CURLOPT_CHUNK_END_FUNCTION: + data->set.chunk_end = va_arg(param, curl_chunk_end_callback); + break; + case CURLOPT_FNMATCH_FUNCTION: + data->set.fnmatch = va_arg(param, curl_fnmatch_callback); + break; + case CURLOPT_CHUNK_DATA: + data->set.wildcardptr = va_arg(param, void *); + break; + case CURLOPT_FNMATCH_DATA: + data->set.fnmatch_data = va_arg(param, void *); + break; +#endif +#ifdef USE_TLS_SRP + case CURLOPT_TLSAUTH_USERNAME: + result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME], + va_arg(param, char *)); + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_TLSAUTH_USERNAME: + result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME_PROXY], + va_arg(param, char *)); + break; +#endif + case CURLOPT_TLSAUTH_PASSWORD: + result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD], + va_arg(param, char *)); + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_TLSAUTH_PASSWORD: + result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD_PROXY], + va_arg(param, char *)); + break; +#endif + case CURLOPT_TLSAUTH_TYPE: + argptr = va_arg(param, char *); + if(argptr && !strncasecompare(argptr, "SRP", strlen("SRP"))) + return CURLE_BAD_FUNCTION_ARGUMENT; + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_TLSAUTH_TYPE: + argptr = va_arg(param, char *); + if(argptr || !strncasecompare(argptr, "SRP", strlen("SRP"))) + return CURLE_BAD_FUNCTION_ARGUMENT; + break; +#endif +#endif +#ifdef USE_ARES + case CURLOPT_DNS_SERVERS: + result = Curl_setstropt(&data->set.str[STRING_DNS_SERVERS], + va_arg(param, char *)); + if(result) + return result; + result = Curl_set_dns_servers(data, data->set.str[STRING_DNS_SERVERS]); + break; + case CURLOPT_DNS_INTERFACE: + result = Curl_setstropt(&data->set.str[STRING_DNS_INTERFACE], + va_arg(param, char *)); + if(result) + return result; + result = Curl_set_dns_interface(data, data->set.str[STRING_DNS_INTERFACE]); + break; + case CURLOPT_DNS_LOCAL_IP4: + result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP4], + va_arg(param, char *)); + if(result) + return result; + result = Curl_set_dns_local_ip4(data, data->set.str[STRING_DNS_LOCAL_IP4]); + break; + case CURLOPT_DNS_LOCAL_IP6: + result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP6], + va_arg(param, char *)); + if(result) + return result; + result = Curl_set_dns_local_ip6(data, data->set.str[STRING_DNS_LOCAL_IP6]); + break; +#endif + case CURLOPT_TCP_KEEPALIVE: + data->set.tcp_keepalive = (0 != va_arg(param, long)); + break; + case CURLOPT_TCP_KEEPIDLE: + arg = va_arg(param, long); + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; + data->set.tcp_keepidle = (int)arg; + break; + case CURLOPT_TCP_KEEPINTVL: + arg = va_arg(param, long); + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; + data->set.tcp_keepintvl = (int)arg; + break; + case CURLOPT_TCP_FASTOPEN: +#if defined(CONNECT_DATA_IDEMPOTENT) || defined(MSG_FASTOPEN) || \ + defined(TCP_FASTOPEN_CONNECT) + data->set.tcp_fastopen = (0 != va_arg(param, long))?TRUE:FALSE; +#else + result = CURLE_NOT_BUILT_IN; +#endif + break; + case CURLOPT_SSL_ENABLE_NPN: + break; + case CURLOPT_SSL_ENABLE_ALPN: + data->set.ssl_enable_alpn = (0 != va_arg(param, long)); + break; +#ifdef USE_UNIX_SOCKETS + case CURLOPT_UNIX_SOCKET_PATH: + data->set.abstract_unix_socket = FALSE; + result = Curl_setstropt(&data->set.str[STRING_UNIX_SOCKET_PATH], + va_arg(param, char *)); + break; + case CURLOPT_ABSTRACT_UNIX_SOCKET: + data->set.abstract_unix_socket = TRUE; + result = Curl_setstropt(&data->set.str[STRING_UNIX_SOCKET_PATH], + va_arg(param, char *)); + break; +#endif + + case CURLOPT_PATH_AS_IS: + data->set.path_as_is = (0 != va_arg(param, long)); + break; + case CURLOPT_PIPEWAIT: + data->set.pipewait = (0 != va_arg(param, long)); + break; + case CURLOPT_STREAM_WEIGHT: +#if defined(USE_HTTP2) || defined(USE_HTTP3) + arg = va_arg(param, long); + if((arg >= 1) && (arg <= 256)) + data->set.priority.weight = (int)arg; + break; +#else + return CURLE_NOT_BUILT_IN; +#endif + case CURLOPT_STREAM_DEPENDS: + case CURLOPT_STREAM_DEPENDS_E: + { + struct Curl_easy *dep = va_arg(param, struct Curl_easy *); + if(!dep || GOOD_EASY_HANDLE(dep)) { + return Curl_data_priority_add_child(dep, data, + option == CURLOPT_STREAM_DEPENDS_E); + } + break; + } + case CURLOPT_CONNECT_TO: + data->set.connect_to = va_arg(param, struct curl_slist *); + break; + case CURLOPT_SUPPRESS_CONNECT_HEADERS: + data->set.suppress_connect_headers = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS: + uarg = va_arg(param, unsigned long); + if(uarg > UINT_MAX) + uarg = UINT_MAX; + data->set.happy_eyeballs_timeout = (unsigned int)uarg; + break; +#ifndef CURL_DISABLE_SHUFFLE_DNS + case CURLOPT_DNS_SHUFFLE_ADDRESSES: + data->set.dns_shuffle_addresses = (0 != va_arg(param, long)); + break; +#endif + case CURLOPT_DISALLOW_USERNAME_IN_URL: + data->set.disallow_username_in_url = (0 != va_arg(param, long)); + break; +#ifndef CURL_DISABLE_DOH + case CURLOPT_DOH_URL: + result = Curl_setstropt(&data->set.str[STRING_DOH], + va_arg(param, char *)); + data->set.doh = data->set.str[STRING_DOH]?TRUE:FALSE; + break; +#endif + case CURLOPT_UPKEEP_INTERVAL_MS: + arg = va_arg(param, long); + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.upkeep_interval_ms = arg; + break; + case CURLOPT_MAXAGE_CONN: + arg = va_arg(param, long); + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.maxage_conn = arg; + break; + case CURLOPT_MAXLIFETIME_CONN: + arg = va_arg(param, long); + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.maxlifetime_conn = arg; + break; + case CURLOPT_TRAILERFUNCTION: +#ifndef CURL_DISABLE_HTTP + data->set.trailer_callback = va_arg(param, curl_trailer_callback); +#endif + break; + case CURLOPT_TRAILERDATA: +#ifndef CURL_DISABLE_HTTP + data->set.trailer_data = va_arg(param, void *); +#endif + break; +#ifndef CURL_DISABLE_HSTS + case CURLOPT_HSTSREADFUNCTION: + data->set.hsts_read = va_arg(param, curl_hstsread_callback); + break; + case CURLOPT_HSTSREADDATA: + data->set.hsts_read_userp = va_arg(param, void *); + break; + case CURLOPT_HSTSWRITEFUNCTION: + data->set.hsts_write = va_arg(param, curl_hstswrite_callback); + break; + case CURLOPT_HSTSWRITEDATA: + data->set.hsts_write_userp = va_arg(param, void *); + break; + case CURLOPT_HSTS: { + struct curl_slist *h; + if(!data->hsts) { + data->hsts = Curl_hsts_init(); + if(!data->hsts) + return CURLE_OUT_OF_MEMORY; + } + argptr = va_arg(param, char *); + if(argptr) { + result = Curl_setstropt(&data->set.str[STRING_HSTS], argptr); + if(result) + return result; + /* this needs to build a list of file names to read from, so that it can + read them later, as we might get a shared HSTS handle to load them + into */ + h = curl_slist_append(data->state.hstslist, argptr); + if(!h) { + curl_slist_free_all(data->state.hstslist); + data->state.hstslist = NULL; + return CURLE_OUT_OF_MEMORY; + } + data->state.hstslist = h; /* store the list for later use */ + } + else { + /* clear the list of HSTS files */ + curl_slist_free_all(data->state.hstslist); + data->state.hstslist = NULL; + if(!data->share || !data->share->hsts) + /* throw away the HSTS cache unless shared */ + Curl_hsts_cleanup(&data->hsts); + } + break; + } + case CURLOPT_HSTS_CTRL: + arg = va_arg(param, long); + if(arg & CURLHSTS_ENABLE) { + if(!data->hsts) { + data->hsts = Curl_hsts_init(); + if(!data->hsts) + return CURLE_OUT_OF_MEMORY; + } + } + else + Curl_hsts_cleanup(&data->hsts); + break; +#endif +#ifndef CURL_DISABLE_ALTSVC + case CURLOPT_ALTSVC: + if(!data->asi) { + data->asi = Curl_altsvc_init(); + if(!data->asi) + return CURLE_OUT_OF_MEMORY; + } + argptr = va_arg(param, char *); + result = Curl_setstropt(&data->set.str[STRING_ALTSVC], argptr); + if(result) + return result; + if(argptr) + (void)Curl_altsvc_load(data->asi, argptr); + break; + case CURLOPT_ALTSVC_CTRL: + if(!data->asi) { + data->asi = Curl_altsvc_init(); + if(!data->asi) + return CURLE_OUT_OF_MEMORY; + } + arg = va_arg(param, long); + result = Curl_altsvc_ctrl(data->asi, arg); + if(result) + return result; + break; +#endif + case CURLOPT_PREREQFUNCTION: + data->set.fprereq = va_arg(param, curl_prereq_callback); + break; + case CURLOPT_PREREQDATA: + data->set.prereq_userp = va_arg(param, void *); + break; +#ifdef USE_WEBSOCKETS + case CURLOPT_WS_OPTIONS: { + bool raw; + arg = va_arg(param, long); + raw = (arg & CURLWS_RAW_MODE); + data->set.ws_raw_mode = raw; + break; + } +#endif + case CURLOPT_QUICK_EXIT: + data->set.quick_exit = (0 != va_arg(param, long)) ? 1L:0L; + break; + default: + /* unknown tag and its companion, just ignore: */ + result = CURLE_UNKNOWN_OPTION; + break; + } + + return result; +} + +/* + * curl_easy_setopt() is the external interface for setting options on an + * easy handle. + * + * 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(struct Curl_easy *data, CURLoption tag, ...) +{ + va_list arg; + CURLcode result; + + if(!data) + return CURLE_BAD_FUNCTION_ARGUMENT; + + va_start(arg, tag); + + result = Curl_vsetopt(data, tag, arg); + + va_end(arg); + return result; +} diff --git a/Utilities/cmcurl/lib/setopt.h b/Utilities/cmcurl/lib/setopt.h new file mode 100644 index 0000000..3c14a05 --- /dev/null +++ b/Utilities/cmcurl/lib/setopt.h @@ -0,0 +1,32 @@ +#ifndef HEADER_CURL_SETOPT_H +#define HEADER_CURL_SETOPT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +CURLcode Curl_setstropt(char **charp, const char *s); +CURLcode Curl_setblobopt(struct curl_blob **blobp, + const struct curl_blob *blob); +CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list arg); + +#endif /* HEADER_CURL_SETOPT_H */ diff --git a/Utilities/cmcurl/lib/setup-os400.h b/Utilities/cmcurl/lib/setup-os400.h new file mode 100644 index 0000000..53e9177 --- /dev/null +++ b/Utilities/cmcurl/lib/setup-os400.h @@ -0,0 +1,144 @@ +#ifndef HEADER_CURL_SETUP_OS400_H +#define HEADER_CURL_SETUP_OS400_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + + +/* OS/400 netdb.h does not define NI_MAXHOST. */ +#define NI_MAXHOST 1025 + +/* OS/400 netdb.h does not define NI_MAXSERV. */ +#define NI_MAXSERV 32 + +/* No OS/400 header file defines u_int32_t. */ +typedef unsigned long u_int32_t; + +/* OS/400 has no idea of a tty! */ +#define isatty(fd) 0 + + +/* System API wrapper prototypes & definitions to support ASCII parameters. */ + +#include <sys/socket.h> +#include <netdb.h> +#include <gskssl.h> +#include <qsoasync.h> +#include <gssapi.h> + +extern int Curl_getaddrinfo_a(const char *nodename, + const char *servname, + const struct addrinfo *hints, + struct addrinfo **res); +#define getaddrinfo Curl_getaddrinfo_a + +/* Note socklen_t must be used as this is declared before curl_socklen_t */ +extern int Curl_getnameinfo_a(const struct sockaddr *sa, + socklen_t salen, + char *nodename, socklen_t nodenamelen, + char *servname, socklen_t servnamelen, + int flags); +#define getnameinfo Curl_getnameinfo_a + +/* GSSAPI wrappers. */ + +extern OM_uint32 Curl_gss_import_name_a(OM_uint32 * minor_status, + gss_buffer_t in_name, + gss_OID in_name_type, + gss_name_t * out_name); +#define gss_import_name Curl_gss_import_name_a + + +extern OM_uint32 Curl_gss_display_status_a(OM_uint32 * minor_status, + OM_uint32 status_value, + int status_type, gss_OID mech_type, + gss_msg_ctx_t * message_context, + gss_buffer_t status_string); +#define gss_display_status Curl_gss_display_status_a + + +extern OM_uint32 Curl_gss_init_sec_context_a(OM_uint32 * minor_status, + gss_cred_id_t cred_handle, + gss_ctx_id_t * context_handle, + gss_name_t target_name, + gss_OID mech_type, + gss_flags_t req_flags, + OM_uint32 time_req, + gss_channel_bindings_t + input_chan_bindings, + gss_buffer_t input_token, + gss_OID * actual_mech_type, + gss_buffer_t output_token, + gss_flags_t * ret_flags, + OM_uint32 * time_rec); +#define gss_init_sec_context Curl_gss_init_sec_context_a + + +extern OM_uint32 Curl_gss_delete_sec_context_a(OM_uint32 * minor_status, + gss_ctx_id_t * context_handle, + gss_buffer_t output_token); +#define gss_delete_sec_context Curl_gss_delete_sec_context_a + + +/* LDAP wrappers. */ + +#define BerValue struct berval + +#define ldap_url_parse ldap_url_parse_utf8 +#define ldap_init Curl_ldap_init_a +#define ldap_simple_bind_s Curl_ldap_simple_bind_s_a +#define ldap_search_s Curl_ldap_search_s_a +#define ldap_get_values_len Curl_ldap_get_values_len_a +#define ldap_err2string Curl_ldap_err2string_a +#define ldap_get_dn Curl_ldap_get_dn_a +#define ldap_first_attribute Curl_ldap_first_attribute_a +#define ldap_next_attribute Curl_ldap_next_attribute_a + +/* Some socket functions must be wrapped to process textual addresses + like AF_UNIX. */ + +extern int Curl_os400_connect(int sd, struct sockaddr *destaddr, int addrlen); +extern int Curl_os400_bind(int sd, struct sockaddr *localaddr, int addrlen); +extern int Curl_os400_sendto(int sd, char *buffer, int buflen, int flags, + const struct sockaddr *dstaddr, int addrlen); +extern int Curl_os400_recvfrom(int sd, char *buffer, int buflen, int flags, + struct sockaddr *fromaddr, int *addrlen); +extern int Curl_os400_getpeername(int sd, struct sockaddr *addr, int *addrlen); +extern int Curl_os400_getsockname(int sd, struct sockaddr *addr, int *addrlen); + +#define connect Curl_os400_connect +#define bind Curl_os400_bind +#define sendto Curl_os400_sendto +#define recvfrom Curl_os400_recvfrom +#define getpeername Curl_os400_getpeername +#define getsockname Curl_os400_getsockname + +#ifdef HAVE_LIBZ +#define zlibVersion Curl_os400_zlibVersion +#define inflateInit_ Curl_os400_inflateInit_ +#define inflateInit2_ Curl_os400_inflateInit2_ +#define inflate Curl_os400_inflate +#define inflateEnd Curl_os400_inflateEnd +#endif + +#endif /* HEADER_CURL_SETUP_OS400_H */ diff --git a/Utilities/cmcurl/lib/setup-vms.h b/Utilities/cmcurl/lib/setup-vms.h new file mode 100644 index 0000000..645cc1a --- /dev/null +++ b/Utilities/cmcurl/lib/setup-vms.h @@ -0,0 +1,444 @@ +#ifndef HEADER_CURL_SETUP_VMS_H +#define HEADER_CURL_SETUP_VMS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* */ +/* JEM, 12/30/12, VMS now generates config.h, so only define wrappers for */ +/* getenv(), getpwuid() and provide is_vms_shell() */ +/* Also need upper case symbols for system services, and */ +/* OpenSSL, and some Kerberos image */ + +#ifdef __DECC +#pragma message save +#pragma message disable dollarid +#endif + +/* Hide the stuff we are overriding */ +#define getenv decc_getenv +#ifdef __DECC +# if __INITIAL_POINTER_SIZE != 64 +# define getpwuid decc_getpwuid +# endif +#endif +#include <stdlib.h> +char *decc$getenv(const char *__name); +#include <pwd.h> + +#include <string.h> +#include <unixlib.h> + +#undef getenv +#undef getpwuid +#define getenv vms_getenv +#define getpwuid vms_getpwuid + +/* VAX needs these in upper case when compiling exact case */ +#define sys$assign SYS$ASSIGN +#define sys$dassgn SYS$DASSGN +#define sys$qiow SYS$QIOW + +#ifdef __DECC +# if __INITIAL_POINTER_SIZE +# pragma __pointer_size __save +# endif +#endif + +#if __USE_LONG_GID_T +# define decc_getpwuid DECC$__LONG_GID_GETPWUID +#else +# if __INITIAL_POINTER_SIZE +# define decc_getpwuid decc$__32_getpwuid +# else +# define decc_getpwuid decc$getpwuid +# endif +#endif + + struct passwd *decc_getpwuid(uid_t uid); + +#ifdef __DECC +# if __INITIAL_POINTER_SIZE == 32 +/* Translate the path, but only if the path is a VMS file specification */ +/* The translation is usually only needed for older versions of VMS */ +static char *vms_translate_path(const char *path) +{ + char *unix_path; + char *test_str; + + /* See if the result is in VMS format, if not, we are done */ + /* Assume that this is a PATH, not just some data */ + test_str = strpbrk(path, ":[<^"); + if(!test_str) { + return (char *)path; + } + + unix_path = decc$translate_vms(path); + + if((int)unix_path <= 0) { + /* We can not translate it, so return the original string */ + return (char *)path; + } +} +# else + /* VMS translate path is actually not needed on the current 64 bit */ + /* VMS platforms, so instead of figuring out the pointer settings */ + /* Change it to a noop */ +# define vms_translate_path(__path) __path +# endif +#endif + +#ifdef __DECC +# if __INITIAL_POINTER_SIZE +# pragma __pointer_size __restore +# endif +#endif + +static char *vms_getenv(const char *envvar) +{ + char *result; + char *vms_path; + + /* first use the DECC getenv() function */ + result = decc$getenv(envvar); + if(!result) { + return result; + } + + vms_path = result; + result = vms_translate_path(vms_path); + + /* note that if you backport this to use VAX C RTL, that the VAX C RTL */ + /* may do a malloc(2048) for each call to getenv(), so you will need */ + /* to add a free(vms_path) */ + /* Do not do a free() for DEC C RTL builds, which should be used for */ + /* VMS 5.5-2 and later, even if using GCC */ + + return result; +} + + +static struct passwd vms_passwd_cache; + +static struct passwd *vms_getpwuid(uid_t uid) +{ + struct passwd *my_passwd; + +/* Hack needed to support 64 bit builds, decc_getpwnam is 32 bit only */ +#ifdef __DECC +# if __INITIAL_POINTER_SIZE + __char_ptr32 unix_path; +# else + char *unix_path; +# endif +#else + char *unix_path; +#endif + + my_passwd = decc_getpwuid(uid); + if(!my_passwd) { + return my_passwd; + } + + unix_path = vms_translate_path(my_passwd->pw_dir); + + if((long)unix_path <= 0) { + /* We can not translate it, so return the original string */ + return my_passwd; + } + + /* If no changes needed just return it */ + if(unix_path == my_passwd->pw_dir) { + return my_passwd; + } + + /* Need to copy the structure returned */ + /* Since curl is only using pw_dir, no need to fix up */ + /* the pw_shell when running under Bash */ + vms_passwd_cache.pw_name = my_passwd->pw_name; + vms_passwd_cache.pw_uid = my_passwd->pw_uid; + vms_passwd_cache.pw_gid = my_passwd->pw_uid; + vms_passwd_cache.pw_dir = unix_path; + vms_passwd_cache.pw_shell = my_passwd->pw_shell; + + return &vms_passwd_cache; +} + +#ifdef __DECC +#pragma message restore +#endif + +/* Bug - VMS OpenSSL and Kerberos universal symbols are in uppercase only */ +/* VMS libraries should have universal symbols in exact and uppercase */ + +#define ASN1_INTEGER_get ASN1_INTEGER_GET +#define ASN1_STRING_data ASN1_STRING_DATA +#define ASN1_STRING_length ASN1_STRING_LENGTH +#define ASN1_STRING_print ASN1_STRING_PRINT +#define ASN1_STRING_to_UTF8 ASN1_STRING_TO_UTF8 +#define ASN1_STRING_type ASN1_STRING_TYPE +#define BIO_ctrl BIO_CTRL +#define BIO_free BIO_FREE +#define BIO_new BIO_NEW +#define BIO_s_mem BIO_S_MEM +#define BN_bn2bin BN_BN2BIN +#define BN_num_bits BN_NUM_BITS +#define CRYPTO_cleanup_all_ex_data CRYPTO_CLEANUP_ALL_EX_DATA +#define CRYPTO_free CRYPTO_FREE +#define CRYPTO_malloc CRYPTO_MALLOC +#define CONF_modules_load_file CONF_MODULES_LOAD_FILE +#ifdef __VAX +# ifdef VMS_OLD_SSL + /* Ancient OpenSSL on VAX/VMS missing this constant */ +# define CONF_MFLAGS_IGNORE_MISSING_FILE 0x10 +# undef CONF_modules_load_file + static int CONF_modules_load_file(const char *filename, + const char *appname, + unsigned long flags) { + return 1; + } +# endif +#endif +#define DES_ecb_encrypt DES_ECB_ENCRYPT +#define DES_set_key DES_SET_KEY +#define DES_set_odd_parity DES_SET_ODD_PARITY +#define ENGINE_ctrl ENGINE_CTRL +#define ENGINE_ctrl_cmd ENGINE_CTRL_CMD +#define ENGINE_finish ENGINE_FINISH +#define ENGINE_free ENGINE_FREE +#define ENGINE_get_first ENGINE_GET_FIRST +#define ENGINE_get_id ENGINE_GET_ID +#define ENGINE_get_next ENGINE_GET_NEXT +#define ENGINE_init ENGINE_INIT +#define ENGINE_load_builtin_engines ENGINE_LOAD_BUILTIN_ENGINES +#define ENGINE_load_private_key ENGINE_LOAD_PRIVATE_KEY +#define ENGINE_set_default ENGINE_SET_DEFAULT +#define ERR_clear_error ERR_CLEAR_ERROR +#define ERR_error_string ERR_ERROR_STRING +#define ERR_error_string_n ERR_ERROR_STRING_N +#define ERR_free_strings ERR_FREE_STRINGS +#define ERR_get_error ERR_GET_ERROR +#define ERR_peek_error ERR_PEEK_ERROR +#define ERR_remove_state ERR_REMOVE_STATE +#define EVP_PKEY_copy_parameters EVP_PKEY_COPY_PARAMETERS +#define EVP_PKEY_free EVP_PKEY_FREE +#define EVP_cleanup EVP_CLEANUP +#define GENERAL_NAMES_free GENERAL_NAMES_FREE +#define i2d_X509_PUBKEY I2D_X509_PUBKEY +#define MD4_Final MD4_FINAL +#define MD4_Init MD4_INIT +#define MD4_Update MD4_UPDATE +#define MD5_Final MD5_FINAL +#define MD5_Init MD5_INIT +#define MD5_Update MD5_UPDATE +#define OPENSSL_add_all_algo_noconf OPENSSL_ADD_ALL_ALGO_NOCONF +#ifndef __VAX +#define OPENSSL_load_builtin_modules OPENSSL_LOAD_BUILTIN_MODULES +#endif +#define PEM_read_X509 PEM_READ_X509 +#define PEM_write_bio_X509 PEM_WRITE_BIO_X509 +#define PKCS12_PBE_add PKCS12_PBE_ADD +#define PKCS12_free PKCS12_FREE +#define PKCS12_parse PKCS12_PARSE +#define RAND_add RAND_ADD +#define RAND_bytes RAND_BYTES +#define RAND_file_name RAND_FILE_NAME +#define RAND_load_file RAND_LOAD_FILE +#define RAND_status RAND_STATUS +#define SSL_CIPHER_get_name SSL_CIPHER_GET_NAME +#define SSL_CTX_add_client_CA SSL_CTX_ADD_CLIENT_CA +#define SSL_CTX_callback_ctrl SSL_CTX_CALLBACK_CTRL +#define SSL_CTX_check_private_key SSL_CTX_CHECK_PRIVATE_KEY +#define SSL_CTX_ctrl SSL_CTX_CTRL +#define SSL_CTX_free SSL_CTX_FREE +#define SSL_CTX_get_cert_store SSL_CTX_GET_CERT_STORE +#define SSL_CTX_load_verify_locations SSL_CTX_LOAD_VERIFY_LOCATIONS +#define SSL_CTX_new SSL_CTX_NEW +#define SSL_CTX_set_cipher_list SSL_CTX_SET_CIPHER_LIST +#define SSL_CTX_set_def_passwd_cb_ud SSL_CTX_SET_DEF_PASSWD_CB_UD +#define SSL_CTX_set_default_passwd_cb SSL_CTX_SET_DEFAULT_PASSWD_CB +#define SSL_CTX_set_msg_callback SSL_CTX_SET_MSG_CALLBACK +#define SSL_CTX_set_verify SSL_CTX_SET_VERIFY +#define SSL_CTX_use_PrivateKey SSL_CTX_USE_PRIVATEKEY +#define SSL_CTX_use_PrivateKey_file SSL_CTX_USE_PRIVATEKEY_FILE +#define SSL_CTX_use_cert_chain_file SSL_CTX_USE_CERT_CHAIN_FILE +#define SSL_CTX_use_certificate SSL_CTX_USE_CERTIFICATE +#define SSL_CTX_use_certificate_file SSL_CTX_USE_CERTIFICATE_FILE +#define SSL_SESSION_free SSL_SESSION_FREE +#define SSL_connect SSL_CONNECT +#define SSL_free SSL_FREE +#define SSL_get1_session SSL_GET1_SESSION +#define SSL_get_certificate SSL_GET_CERTIFICATE +#define SSL_get_current_cipher SSL_GET_CURRENT_CIPHER +#define SSL_get_error SSL_GET_ERROR +#define SSL_get_peer_cert_chain SSL_GET_PEER_CERT_CHAIN +#define SSL_get_peer_certificate SSL_GET_PEER_CERTIFICATE +#define SSL_get_privatekey SSL_GET_PRIVATEKEY +#define SSL_get_session SSL_GET_SESSION +#define SSL_get_shutdown SSL_GET_SHUTDOWN +#define SSL_get_verify_result SSL_GET_VERIFY_RESULT +#define SSL_library_init SSL_LIBRARY_INIT +#define SSL_load_error_strings SSL_LOAD_ERROR_STRINGS +#define SSL_new SSL_NEW +#define SSL_peek SSL_PEEK +#define SSL_pending SSL_PENDING +#define SSL_read SSL_READ +#define SSL_set_connect_state SSL_SET_CONNECT_STATE +#define SSL_set_fd SSL_SET_FD +#define SSL_set_session SSL_SET_SESSION +#define SSL_shutdown SSL_SHUTDOWN +#define SSL_version SSL_VERSION +#define SSL_write SSL_WRITE +#define SSLeay SSLEAY +#define SSLv23_client_method SSLV23_CLIENT_METHOD +#define SSLv3_client_method SSLV3_CLIENT_METHOD +#define TLSv1_client_method TLSV1_CLIENT_METHOD +#define UI_create_method UI_CREATE_METHOD +#define UI_destroy_method UI_DESTROY_METHOD +#define UI_get0_user_data UI_GET0_USER_DATA +#define UI_get_input_flags UI_GET_INPUT_FLAGS +#define UI_get_string_type UI_GET_STRING_TYPE +#define UI_create_method UI_CREATE_METHOD +#define UI_destroy_method UI_DESTROY_METHOD +#define UI_method_get_closer UI_METHOD_GET_CLOSER +#define UI_method_get_opener UI_METHOD_GET_OPENER +#define UI_method_get_reader UI_METHOD_GET_READER +#define UI_method_get_writer UI_METHOD_GET_WRITER +#define UI_method_set_closer UI_METHOD_SET_CLOSER +#define UI_method_set_opener UI_METHOD_SET_OPENER +#define UI_method_set_reader UI_METHOD_SET_READER +#define UI_method_set_writer UI_METHOD_SET_WRITER +#define UI_OpenSSL UI_OPENSSL +#define UI_set_result UI_SET_RESULT +#define X509V3_EXT_print X509V3_EXT_PRINT +#define X509_EXTENSION_get_critical X509_EXTENSION_GET_CRITICAL +#define X509_EXTENSION_get_data X509_EXTENSION_GET_DATA +#define X509_EXTENSION_get_object X509_EXTENSION_GET_OBJECT +#define X509_LOOKUP_file X509_LOOKUP_FILE +#define X509_NAME_ENTRY_get_data X509_NAME_ENTRY_GET_DATA +#define X509_NAME_get_entry X509_NAME_GET_ENTRY +#define X509_NAME_get_index_by_NID X509_NAME_GET_INDEX_BY_NID +#define X509_NAME_print_ex X509_NAME_PRINT_EX +#define X509_STORE_CTX_get_current_cert X509_STORE_CTX_GET_CURRENT_CERT +#define X509_STORE_add_lookup X509_STORE_ADD_LOOKUP +#define X509_STORE_set_flags X509_STORE_SET_FLAGS +#define X509_check_issued X509_CHECK_ISSUED +#define X509_free X509_FREE +#define X509_get_ext_d2i X509_GET_EXT_D2I +#define X509_get_issuer_name X509_GET_ISSUER_NAME +#define X509_get_pubkey X509_GET_PUBKEY +#define X509_get_serialNumber X509_GET_SERIALNUMBER +#define X509_get_subject_name X509_GET_SUBJECT_NAME +#define X509_load_crl_file X509_LOAD_CRL_FILE +#define X509_verify_cert_error_string X509_VERIFY_CERT_ERROR_STRING +#define d2i_PKCS12_fp D2I_PKCS12_FP +#define i2t_ASN1_OBJECT I2T_ASN1_OBJECT +#define sk_num SK_NUM +#define sk_pop SK_POP +#define sk_pop_free SK_POP_FREE +#define sk_value SK_VALUE +#ifdef __VAX +#define OPENSSL_NO_SHA256 +#endif +#define SHA256_Final SHA256_FINAL +#define SHA256_Init SHA256_INIT +#define SHA256_Update SHA256_UPDATE + +#define USE_UPPERCASE_GSSAPI 1 +#define gss_seal GSS_SEAL +#define gss_unseal GSS_UNSEAL + +#define USE_UPPERCASE_KRBAPI 1 + +/* AI_NUMERICHOST needed for IP V6 support in Curl */ +#ifdef HAVE_NETDB_H +#include <netdb.h> +#ifndef AI_NUMERICHOST +#ifdef ENABLE_IPV6 +#undef ENABLE_IPV6 +#endif +#endif +#endif + +/* VAX symbols are always in uppercase */ +#ifdef __VAX +#define inflate INFLATE +#define inflateEnd INFLATEEND +#define inflateInit2_ INFLATEINIT2_ +#define inflateInit_ INFLATEINIT_ +#define zlibVersion ZLIBVERSION +#endif + +/* Older VAX OpenSSL port defines these as Macros */ +/* Need to include the headers first and then redefine */ +/* that way a newer port will also work if some one has one */ +#ifdef __VAX + +# if (OPENSSL_VERSION_NUMBER < 0x00907001L) +# define des_set_odd_parity DES_SET_ODD_PARITY +# define des_set_key DES_SET_KEY +# define des_ecb_encrypt DES_ECB_ENCRYPT + +# endif +# include <openssl/evp.h> +# ifndef OpenSSL_add_all_algorithms +# define OpenSSL_add_all_algorithms OPENSSL_ADD_ALL_ALGORITHMS + void OPENSSL_ADD_ALL_ALGORITHMS(void); +# endif + + /* Curl defines these to lower case and VAX needs them in upper case */ + /* So we need static routines */ +# if (OPENSSL_VERSION_NUMBER < 0x00907001L) + +# undef des_set_odd_parity +# undef DES_set_odd_parity +# undef des_set_key +# undef DES_set_key +# undef des_ecb_encrypt +# undef DES_ecb_encrypt + + static void des_set_odd_parity(des_cblock *key) { + DES_SET_ODD_PARITY(key); + } + + static int des_set_key(const_des_cblock *key, + des_key_schedule schedule) { + return DES_SET_KEY(key, schedule); + } + + static void des_ecb_encrypt(const_des_cblock *input, + des_cblock *output, + des_key_schedule ks, int enc) { + DES_ECB_ENCRYPT(input, output, ks, enc); + } +#endif +/* Need this to stop a macro redefinition error */ +#if OPENSSL_VERSION_NUMBER < 0x00907000L +# ifdef X509_STORE_set_flags +# undef X509_STORE_set_flags +# define X509_STORE_set_flags(x,y) Curl_nop_stmt +# endif +#endif +#endif + +#endif /* HEADER_CURL_SETUP_VMS_H */ diff --git a/Utilities/cmcurl/lib/setup-win32.h b/Utilities/cmcurl/lib/setup-win32.h new file mode 100644 index 0000000..4e034d4 --- /dev/null +++ b/Utilities/cmcurl/lib/setup-win32.h @@ -0,0 +1,118 @@ +#ifndef HEADER_CURL_SETUP_WIN32_H +#define HEADER_CURL_SETUP_WIN32_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Include header files for windows builds before redefining anything. + * Use this preprocessor block only to include or exclude windows.h, + * winsock2.h or ws2tcpip.h. Any other windows thing belongs + * to any other further and independent block. Under Cygwin things work + * just as under linux (e.g. <sys/socket.h>) and the winsock headers should + * never be included when __CYGWIN__ is defined. configure script takes + * care of this, not defining HAVE_WINDOWS_H, HAVE_WINSOCK2_H, + * neither HAVE_WS2TCPIP_H when __CYGWIN__ is defined. + */ + +#ifdef HAVE_WINDOWS_H +# if defined(UNICODE) && !defined(_UNICODE) +# error "UNICODE is defined but _UNICODE is not defined" +# endif +# if defined(_UNICODE) && !defined(UNICODE) +# error "_UNICODE is defined but UNICODE is not defined" +# endif +/* + * Don't include unneeded stuff in Windows headers to avoid compiler + * warnings and macro clashes. + * Make sure to define this macro before including any Windows headers. + */ +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# ifndef NOGDI +# define NOGDI +# endif +# ifdef HAVE_WINSOCK2_H +# include <winsock2.h> +# ifdef HAVE_WS2TCPIP_H +# include <ws2tcpip.h> +# endif +# endif +# include <windows.h> +# include <winerror.h> +# include <tchar.h> +# ifdef UNICODE + typedef wchar_t *(*curl_wcsdup_callback)(const wchar_t *str); +# endif +#endif + +/* + * Define USE_WINSOCK to 2 if we have and use WINSOCK2 API, else + * undefine USE_WINSOCK. + */ + +#undef USE_WINSOCK + +#ifdef HAVE_WINSOCK2_H +# define USE_WINSOCK 2 +#endif + +/* + * Define _WIN32_WINNT_[OS] symbols because not all Windows build systems have + * those symbols to compare against, and even those that do may be missing + * newer symbols. + */ + +#ifndef _WIN32_WINNT_NT4 +#define _WIN32_WINNT_NT4 0x0400 /* Windows NT 4.0 */ +#endif +#ifndef _WIN32_WINNT_WIN2K +#define _WIN32_WINNT_WIN2K 0x0500 /* Windows 2000 */ +#endif +#ifndef _WIN32_WINNT_WINXP +#define _WIN32_WINNT_WINXP 0x0501 /* Windows XP */ +#endif +#ifndef _WIN32_WINNT_WS03 +#define _WIN32_WINNT_WS03 0x0502 /* Windows Server 2003 */ +#endif +#ifndef _WIN32_WINNT_VISTA +#define _WIN32_WINNT_VISTA 0x0600 /* Windows Vista */ +#endif +#ifndef _WIN32_WINNT_WS08 +#define _WIN32_WINNT_WS08 0x0600 /* Windows Server 2008 */ +#endif +#ifndef _WIN32_WINNT_WIN7 +#define _WIN32_WINNT_WIN7 0x0601 /* Windows 7 */ +#endif +#ifndef _WIN32_WINNT_WIN8 +#define _WIN32_WINNT_WIN8 0x0602 /* Windows 8 */ +#endif +#ifndef _WIN32_WINNT_WINBLUE +#define _WIN32_WINNT_WINBLUE 0x0603 /* Windows 8.1 */ +#endif +#ifndef _WIN32_WINNT_WIN10 +#define _WIN32_WINNT_WIN10 0x0A00 /* Windows 10 */ +#endif + +#endif /* HEADER_CURL_SETUP_WIN32_H */ diff --git a/Utilities/cmcurl/lib/sha256.c b/Utilities/cmcurl/lib/sha256.c new file mode 100644 index 0000000..4a02045 --- /dev/null +++ b/Utilities/cmcurl/lib/sha256.c @@ -0,0 +1,545 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Florin Petriuc, <petriuc.florin@gmail.com> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) \ + || defined(USE_LIBSSH2) + +#include "warnless.h" +#include "curl_sha256.h" +#include "curl_hmac.h" + +#ifdef USE_WOLFSSL +#include <wolfssl/options.h> +#ifndef NO_SHA256 +#define USE_OPENSSL_SHA256 +#endif +#endif + +#if defined(USE_OPENSSL) + +#include <openssl/opensslv.h> + +#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) +#define USE_OPENSSL_SHA256 +#endif + +#endif /* USE_OPENSSL */ + +#ifdef USE_MBEDTLS +#include <mbedtls/version.h> + +#if(MBEDTLS_VERSION_NUMBER >= 0x02070000) && \ + (MBEDTLS_VERSION_NUMBER < 0x03000000) + #define HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS +#endif +#endif /* USE_MBEDTLS */ + +#if defined(USE_OPENSSL_SHA256) + +/* When OpenSSL or wolfSSL is available we use their SHA256-functions. */ +#if defined(USE_OPENSSL) +#include <openssl/evp.h> +#elif defined(USE_WOLFSSL) +#include <wolfssl/openssl/evp.h> +#endif + +#elif defined(USE_GNUTLS) +#include <nettle/sha.h> +#elif defined(USE_MBEDTLS) +#include <mbedtls/sha256.h> +#elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ + (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040)) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + (__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000)) +#include <CommonCrypto/CommonDigest.h> +#define AN_APPLE_OS +#elif defined(USE_WIN32_CRYPTO) +#include <wincrypt.h> +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* Please keep the SSL backend-specific #if branches in this order: + * + * 1. USE_OPENSSL + * 2. USE_GNUTLS + * 3. USE_MBEDTLS + * 4. USE_COMMON_CRYPTO + * 5. USE_WIN32_CRYPTO + * + * This ensures that the same SSL branch gets activated throughout this source + * file even if multiple backends are enabled at the same time. + */ + +#if defined(USE_OPENSSL_SHA256) + +struct sha256_ctx { + EVP_MD_CTX *openssl_ctx; +}; +typedef struct sha256_ctx my_sha256_ctx; + +static CURLcode my_sha256_init(my_sha256_ctx *ctx) +{ + ctx->openssl_ctx = EVP_MD_CTX_create(); + 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); + return CURLE_FAILED_INIT; + } + return CURLE_OK; +} + +static void my_sha256_update(my_sha256_ctx *ctx, + const unsigned char *data, + unsigned int length) +{ + EVP_DigestUpdate(ctx->openssl_ctx, data, length); +} + +static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +{ + EVP_DigestFinal_ex(ctx->openssl_ctx, digest, NULL); + EVP_MD_CTX_destroy(ctx->openssl_ctx); +} + +#elif defined(USE_GNUTLS) + +typedef struct sha256_ctx my_sha256_ctx; + +static CURLcode my_sha256_init(my_sha256_ctx *ctx) +{ + sha256_init(ctx); + return CURLE_OK; +} + +static void my_sha256_update(my_sha256_ctx *ctx, + const unsigned char *data, + unsigned int length) +{ + sha256_update(ctx, length, data); +} + +static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +{ + sha256_digest(ctx, SHA256_DIGEST_SIZE, digest); +} + +#elif defined(USE_MBEDTLS) + +typedef mbedtls_sha256_context my_sha256_ctx; + +static CURLcode my_sha256_init(my_sha256_ctx *ctx) +{ +#if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) + (void) mbedtls_sha256_starts(ctx, 0); +#else + (void) mbedtls_sha256_starts_ret(ctx, 0); +#endif + return CURLE_OK; +} + +static void my_sha256_update(my_sha256_ctx *ctx, + const unsigned char *data, + unsigned int length) +{ +#if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) + (void) mbedtls_sha256_update(ctx, data, length); +#else + (void) mbedtls_sha256_update_ret(ctx, data, length); +#endif +} + +static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +{ +#if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) + (void) mbedtls_sha256_finish(ctx, digest); +#else + (void) mbedtls_sha256_finish_ret(ctx, digest); +#endif +} + +#elif defined(AN_APPLE_OS) +typedef CC_SHA256_CTX my_sha256_ctx; + +static CURLcode my_sha256_init(my_sha256_ctx *ctx) +{ + (void) CC_SHA256_Init(ctx); + return CURLE_OK; +} + +static void my_sha256_update(my_sha256_ctx *ctx, + const unsigned char *data, + unsigned int length) +{ + (void) CC_SHA256_Update(ctx, data, length); +} + +static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +{ + (void) CC_SHA256_Final(digest, ctx); +} + +#elif defined(USE_WIN32_CRYPTO) + +struct sha256_ctx { + HCRYPTPROV hCryptProv; + HCRYPTHASH hHash; +}; +typedef struct sha256_ctx my_sha256_ctx; + +#if !defined(CALG_SHA_256) +#define CALG_SHA_256 0x0000800c +#endif + +static CURLcode my_sha256_init(my_sha256_ctx *ctx) +{ + if(!CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, PROV_RSA_AES, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + return CURLE_OUT_OF_MEMORY; + + if(!CryptCreateHash(ctx->hCryptProv, CALG_SHA_256, 0, 0, &ctx->hHash)) { + CryptReleaseContext(ctx->hCryptProv, 0); + ctx->hCryptProv = 0; + return CURLE_FAILED_INIT; + } + + return CURLE_OK; +} + +static void my_sha256_update(my_sha256_ctx *ctx, + const unsigned char *data, + unsigned int length) +{ + CryptHashData(ctx->hHash, (unsigned char *) data, length, 0); +} + +static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +{ + unsigned long length = 0; + + CryptGetHashParam(ctx->hHash, HP_HASHVAL, NULL, &length, 0); + if(length == SHA256_DIGEST_LENGTH) + CryptGetHashParam(ctx->hHash, HP_HASHVAL, digest, &length, 0); + + if(ctx->hHash) + CryptDestroyHash(ctx->hHash); + + if(ctx->hCryptProv) + CryptReleaseContext(ctx->hCryptProv, 0); +} + +#else + +/* When no other crypto library is available we use this code segment */ + +/* This is based on SHA256 implementation in LibTomCrypt that was released into + * public domain by Tom St Denis. */ + +#define WPA_GET_BE32(a) ((((unsigned long)(a)[0]) << 24) | \ + (((unsigned long)(a)[1]) << 16) | \ + (((unsigned long)(a)[2]) << 8) | \ + ((unsigned long)(a)[3])) +#define WPA_PUT_BE32(a, val) \ +do { \ + (a)[0] = (unsigned char)((((unsigned long) (val)) >> 24) & 0xff); \ + (a)[1] = (unsigned char)((((unsigned long) (val)) >> 16) & 0xff); \ + (a)[2] = (unsigned char)((((unsigned long) (val)) >> 8) & 0xff); \ + (a)[3] = (unsigned char)(((unsigned long) (val)) & 0xff); \ +} while(0) + +#ifdef HAVE_LONGLONG +#define WPA_PUT_BE64(a, val) \ +do { \ + (a)[0] = (unsigned char)(((unsigned long long)(val)) >> 56); \ + (a)[1] = (unsigned char)(((unsigned long long)(val)) >> 48); \ + (a)[2] = (unsigned char)(((unsigned long long)(val)) >> 40); \ + (a)[3] = (unsigned char)(((unsigned long long)(val)) >> 32); \ + (a)[4] = (unsigned char)(((unsigned long long)(val)) >> 24); \ + (a)[5] = (unsigned char)(((unsigned long long)(val)) >> 16); \ + (a)[6] = (unsigned char)(((unsigned long long)(val)) >> 8); \ + (a)[7] = (unsigned char)(((unsigned long long)(val)) & 0xff); \ +} while(0) +#else +#define WPA_PUT_BE64(a, val) \ +do { \ + (a)[0] = (unsigned char)(((unsigned __int64)(val)) >> 56); \ + (a)[1] = (unsigned char)(((unsigned __int64)(val)) >> 48); \ + (a)[2] = (unsigned char)(((unsigned __int64)(val)) >> 40); \ + (a)[3] = (unsigned char)(((unsigned __int64)(val)) >> 32); \ + (a)[4] = (unsigned char)(((unsigned __int64)(val)) >> 24); \ + (a)[5] = (unsigned char)(((unsigned __int64)(val)) >> 16); \ + (a)[6] = (unsigned char)(((unsigned __int64)(val)) >> 8); \ + (a)[7] = (unsigned char)(((unsigned __int64)(val)) & 0xff); \ +} while(0) +#endif + +struct sha256_state { +#ifdef HAVE_LONGLONG + unsigned long long length; +#else + unsigned __int64 length; +#endif + unsigned long state[8], curlen; + unsigned char buf[64]; +}; +typedef struct sha256_state my_sha256_ctx; + +/* The K array */ +static const unsigned long K[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, + 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL, + 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, + 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL, + 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, + 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, + 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, + 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL, + 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, + 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL +}; + +/* Various logical functions */ +#define RORc(x, y) \ +(((((unsigned long)(x) & 0xFFFFFFFFUL) >> (unsigned long)((y) & 31)) | \ + ((unsigned long)(x) << (unsigned long)(32 - ((y) & 31)))) & 0xFFFFFFFFUL) +#define Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Maj(x,y,z) (((x | y) & z) | (x & y)) +#define S(x, n) RORc((x), (n)) +#define R(x, n) (((x)&0xFFFFFFFFUL)>>(n)) +#define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22)) +#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25)) +#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3)) +#define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10)) + +/* Compress 512-bits */ +static int sha256_compress(struct sha256_state *md, + unsigned char *buf) +{ + unsigned long S[8], W[64]; + int i; + + /* Copy state into S */ + for(i = 0; i < 8; i++) { + S[i] = md->state[i]; + } + /* copy the state into 512-bits into W[0..15] */ + for(i = 0; i < 16; i++) + W[i] = WPA_GET_BE32(buf + (4 * i)); + /* fill W[16..63] */ + for(i = 16; i < 64; i++) { + W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + + W[i - 16]; + } + + /* Compress */ +#define RND(a,b,c,d,e,f,g,h,i) \ + do { \ + unsigned long t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ + unsigned long t1 = Sigma0(a) + Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; \ + } while(0) + + for(i = 0; i < 64; ++i) { + unsigned long t; + RND(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i); + t = S[7]; S[7] = S[6]; S[6] = S[5]; S[5] = S[4]; + S[4] = S[3]; S[3] = S[2]; S[2] = S[1]; S[1] = S[0]; S[0] = t; + } + + /* Feedback */ + for(i = 0; i < 8; i++) { + md->state[i] = md->state[i] + S[i]; + } + + return 0; +} + +/* Initialize the hash state */ +static CURLcode my_sha256_init(struct sha256_state *md) +{ + md->curlen = 0; + md->length = 0; + md->state[0] = 0x6A09E667UL; + md->state[1] = 0xBB67AE85UL; + md->state[2] = 0x3C6EF372UL; + md->state[3] = 0xA54FF53AUL; + md->state[4] = 0x510E527FUL; + md->state[5] = 0x9B05688CUL; + md->state[6] = 0x1F83D9ABUL; + md->state[7] = 0x5BE0CD19UL; + + return CURLE_OK; +} + +/* + 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) + @return 0 if successful +*/ +static int my_sha256_update(struct sha256_state *md, + const unsigned char *in, + unsigned long inlen) +{ + unsigned long n; + +#define block_size 64 + if(md->curlen > sizeof(md->buf)) + return -1; + while(inlen > 0) { + if(md->curlen == 0 && inlen >= block_size) { + if(sha256_compress(md, (unsigned char *)in) < 0) + return -1; + md->length += block_size * 8; + in += block_size; + inlen -= block_size; + } + else { + n = CURLMIN(inlen, (block_size - md->curlen)); + memcpy(md->buf + md->curlen, in, n); + md->curlen += n; + in += n; + inlen -= n; + if(md->curlen == block_size) { + if(sha256_compress(md, md->buf) < 0) + return -1; + md->length += 8 * block_size; + md->curlen = 0; + } + } + } + + return 0; +} + +/* + 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 int my_sha256_final(unsigned char *out, + struct sha256_state *md) +{ + int i; + + if(md->curlen >= sizeof(md->buf)) + return -1; + + /* Increase the length of the message */ + md->length += md->curlen * 8; + + /* Append the '1' bit */ + md->buf[md->curlen++] = (unsigned char)0x80; + + /* If the length is currently above 56 bytes we append zeros + * then compress. Then we can fall back to padding zeros and length + * encoding like normal. + */ + if(md->curlen > 56) { + while(md->curlen < 64) { + md->buf[md->curlen++] = (unsigned char)0; + } + sha256_compress(md, md->buf); + md->curlen = 0; + } + + /* Pad up to 56 bytes of zeroes */ + while(md->curlen < 56) { + md->buf[md->curlen++] = (unsigned char)0; + } + + /* Store length */ + WPA_PUT_BE64(md->buf + 56, md->length); + sha256_compress(md, md->buf); + + /* Copy output */ + for(i = 0; i < 8; i++) + WPA_PUT_BE32(out + (4 * i), md->state[i]); + + return 0; +} + +#endif /* CRYPTO LIBS */ + +/* + * Curl_sha256it() + * + * Generates a SHA256 hash for the given input data. + * + * Parameters: + * + * output [in/out] - The output buffer. + * input [in] - The input data. + * length [in] - The input length. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_sha256it(unsigned char *output, const unsigned char *input, + const size_t length) +{ + CURLcode result; + my_sha256_ctx ctx; + + result = my_sha256_init(&ctx); + if(!result) { + my_sha256_update(&ctx, input, curlx_uztoui(length)); + my_sha256_final(output, &ctx); + } + return result; +} + + +const struct HMAC_params Curl_HMAC_SHA256[] = { + { + /* Hash initialization function. */ + CURLX_FUNCTION_CAST(HMAC_hinit_func, my_sha256_init), + /* Hash update function. */ + CURLX_FUNCTION_CAST(HMAC_hupdate_func, my_sha256_update), + /* Hash computation end function. */ + CURLX_FUNCTION_CAST(HMAC_hfinal_func, my_sha256_final), + /* Size of hash context structure. */ + sizeof(my_sha256_ctx), + /* Maximum key length. */ + 64, + /* Result size. */ + 32 + } +}; + + +#endif /* AWS, DIGEST, or libSSH2 */ diff --git a/Utilities/cmcurl/lib/share.c b/Utilities/cmcurl/lib/share.c new file mode 100644 index 0000000..c0a8d80 --- /dev/null +++ b/Utilities/cmcurl/lib/share.c @@ -0,0 +1,290 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> +#include "urldata.h" +#include "share.h" +#include "psl.h" +#include "vtls/vtls.h" +#include "hsts.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +struct Curl_share * +curl_share_init(void) +{ + struct Curl_share *share = calloc(1, sizeof(struct Curl_share)); + if(share) { + share->magic = CURL_GOOD_SHARE; + share->specifier |= (1<<CURL_LOCK_DATA_SHARE); + Curl_init_dnscache(&share->hostcache, 23); + } + + return share; +} + +#undef curl_share_setopt +CURLSHcode +curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) +{ + va_list param; + int type; + curl_lock_function lockfunc; + curl_unlock_function unlockfunc; + void *ptr; + CURLSHcode res = CURLSHE_OK; + + if(!GOOD_SHARE_HANDLE(share)) + return CURLSHE_INVALID; + + if(share->dirty) + /* don't allow setting options while one or more handles are already + using this share */ + return CURLSHE_IN_USE; + + va_start(param, option); + + switch(option) { + case CURLSHOPT_SHARE: + /* this is a type this share will share */ + type = va_arg(param, int); + + switch(type) { + case CURL_LOCK_DATA_DNS: + break; + + case CURL_LOCK_DATA_COOKIE: +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(!share->cookies) { + share->cookies = Curl_cookie_init(NULL, NULL, NULL, TRUE); + if(!share->cookies) + res = CURLSHE_NOMEM; + } +#else /* CURL_DISABLE_HTTP */ + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + + case CURL_LOCK_DATA_HSTS: +#ifndef CURL_DISABLE_HSTS + if(!share->hsts) { + share->hsts = Curl_hsts_init(); + if(!share->hsts) + res = CURLSHE_NOMEM; + } +#else /* CURL_DISABLE_HSTS */ + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + + case CURL_LOCK_DATA_SSL_SESSION: +#ifdef USE_SSL + if(!share->sslsession) { + share->max_ssl_sessions = 8; + share->sslsession = calloc(share->max_ssl_sessions, + sizeof(struct Curl_ssl_session)); + share->sessionage = 0; + if(!share->sslsession) + res = CURLSHE_NOMEM; + } +#else + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + + case CURL_LOCK_DATA_CONNECT: + if(Curl_conncache_init(&share->conn_cache, 103)) + res = CURLSHE_NOMEM; + break; + + case CURL_LOCK_DATA_PSL: +#ifndef USE_LIBPSL + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + + default: + res = CURLSHE_BAD_OPTION; + } + if(!res) + share->specifier |= (1<<type); + break; + + case CURLSHOPT_UNSHARE: + /* this is a type this share will no longer share */ + type = va_arg(param, int); + share->specifier &= ~(1<<type); + switch(type) { + case CURL_LOCK_DATA_DNS: + break; + + case CURL_LOCK_DATA_COOKIE: +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(share->cookies) { + Curl_cookie_cleanup(share->cookies); + share->cookies = NULL; + } +#else /* CURL_DISABLE_HTTP */ + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + + case CURL_LOCK_DATA_HSTS: +#ifndef CURL_DISABLE_HSTS + if(share->hsts) { + Curl_hsts_cleanup(&share->hsts); + } +#else /* CURL_DISABLE_HSTS */ + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + + case CURL_LOCK_DATA_SSL_SESSION: +#ifdef USE_SSL + Curl_safefree(share->sslsession); +#else + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + + case CURL_LOCK_DATA_CONNECT: + break; + + default: + res = CURLSHE_BAD_OPTION; + break; + } + break; + + case CURLSHOPT_LOCKFUNC: + lockfunc = va_arg(param, curl_lock_function); + share->lockfunc = lockfunc; + break; + + case CURLSHOPT_UNLOCKFUNC: + unlockfunc = va_arg(param, curl_unlock_function); + share->unlockfunc = unlockfunc; + break; + + case CURLSHOPT_USERDATA: + ptr = va_arg(param, void *); + share->clientdata = ptr; + break; + + default: + res = CURLSHE_BAD_OPTION; + break; + } + + va_end(param); + + return res; +} + +CURLSHcode +curl_share_cleanup(struct Curl_share *share) +{ + if(!GOOD_SHARE_HANDLE(share)) + return CURLSHE_INVALID; + + if(share->lockfunc) + share->lockfunc(NULL, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE, + share->clientdata); + + if(share->dirty) { + if(share->unlockfunc) + share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata); + return CURLSHE_IN_USE; + } + + Curl_conncache_close_all_connections(&share->conn_cache); + Curl_conncache_destroy(&share->conn_cache); + Curl_hash_destroy(&share->hostcache); + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + Curl_cookie_cleanup(share->cookies); +#endif + +#ifndef CURL_DISABLE_HSTS + Curl_hsts_cleanup(&share->hsts); +#endif + +#ifdef USE_SSL + if(share->sslsession) { + size_t i; + for(i = 0; i < share->max_ssl_sessions; i++) + Curl_ssl_kill_session(&(share->sslsession[i])); + free(share->sslsession); + } +#endif + + Curl_psl_destroy(&share->psl); + + if(share->unlockfunc) + share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata); + share->magic = 0; + free(share); + + return CURLSHE_OK; +} + + +CURLSHcode +Curl_share_lock(struct Curl_easy *data, curl_lock_data type, + curl_lock_access accesstype) +{ + struct Curl_share *share = data->share; + + if(!share) + return CURLSHE_INVALID; + + if(share->specifier & (1<<type)) { + if(share->lockfunc) /* only call this if set! */ + share->lockfunc(data, type, accesstype, share->clientdata); + } + /* else if we don't share this, pretend successful lock */ + + return CURLSHE_OK; +} + +CURLSHcode +Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) +{ + struct Curl_share *share = data->share; + + if(!share) + return CURLSHE_INVALID; + + if(share->specifier & (1<<type)) { + if(share->unlockfunc) /* only call this if set! */ + share->unlockfunc (data, type, share->clientdata); + } + + return CURLSHE_OK; +} diff --git a/Utilities/cmcurl/lib/share.h b/Utilities/cmcurl/lib/share.h new file mode 100644 index 0000000..632d919 --- /dev/null +++ b/Utilities/cmcurl/lib/share.h @@ -0,0 +1,68 @@ +#ifndef HEADER_CURL_SHARE_H +#define HEADER_CURL_SHARE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include <curl/curl.h> +#include "cookie.h" +#include "psl.h" +#include "urldata.h" +#include "conncache.h" + +#define CURL_GOOD_SHARE 0x7e117a1e +#define GOOD_SHARE_HANDLE(x) ((x) && (x)->magic == CURL_GOOD_SHARE) + +/* this struct is libcurl-private, don't export details */ +struct Curl_share { + unsigned int magic; /* CURL_GOOD_SHARE */ + unsigned int specifier; + volatile unsigned int dirty; + + curl_lock_function lockfunc; + curl_unlock_function unlockfunc; + void *clientdata; + struct conncache conn_cache; + struct Curl_hash hostcache; +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + struct CookieInfo *cookies; +#endif +#ifdef USE_LIBPSL + struct PslCache psl; +#endif +#ifndef CURL_DISABLE_HSTS + struct hsts *hsts; +#endif +#ifdef USE_SSL + struct Curl_ssl_session *sslsession; + size_t max_ssl_sessions; + long sessionage; +#endif +}; + +CURLSHcode Curl_share_lock(struct Curl_easy *, curl_lock_data, + curl_lock_access); +CURLSHcode Curl_share_unlock(struct Curl_easy *, curl_lock_data); + +#endif /* HEADER_CURL_SHARE_H */ diff --git a/Utilities/cmcurl/lib/sigpipe.h b/Utilities/cmcurl/lib/sigpipe.h new file mode 100644 index 0000000..9b29403 --- /dev/null +++ b/Utilities/cmcurl/lib/sigpipe.h @@ -0,0 +1,80 @@ +#ifndef HEADER_CURL_SIGPIPE_H +#define HEADER_CURL_SIGPIPE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if defined(HAVE_SIGACTION) && \ + (defined(USE_OPENSSL) || defined(USE_MBEDTLS) || defined(USE_WOLFSSL)) +#include <signal.h> + +struct sigpipe_ignore { + struct sigaction old_pipe_act; + bool no_signal; +}; + +#define SIGPIPE_VARIABLE(x) struct sigpipe_ignore x + +/* + * sigpipe_ignore() makes sure we ignore SIGPIPE while running libcurl + * internals, and then sigpipe_restore() will restore the situation when we + * return from libcurl again. + */ +static void sigpipe_ignore(struct Curl_easy *data, + struct sigpipe_ignore *ig) +{ + /* get a local copy of no_signal because the Curl_easy might not be + around when we restore */ + ig->no_signal = data->set.no_signal; + if(!data->set.no_signal) { + struct sigaction action; + /* first, extract the existing situation */ + sigaction(SIGPIPE, NULL, &ig->old_pipe_act); + action = ig->old_pipe_act; + /* ignore this signal */ + action.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &action, NULL); + } +} + +/* + * sigpipe_restore() puts back the outside world's opinion of signal handler + * and SIGPIPE handling. It MUST only be called after a corresponding + * sigpipe_ignore() was used. + */ +static void sigpipe_restore(struct sigpipe_ignore *ig) +{ + if(!ig->no_signal) + /* restore the outside state */ + sigaction(SIGPIPE, &ig->old_pipe_act, NULL); +} + +#else +/* for systems without sigaction */ +#define sigpipe_ignore(x,y) Curl_nop_stmt +#define sigpipe_restore(x) Curl_nop_stmt +#define SIGPIPE_VARIABLE(x) +#endif + +#endif /* HEADER_CURL_SIGPIPE_H */ diff --git a/Utilities/cmcurl/lib/slist.c b/Utilities/cmcurl/lib/slist.c new file mode 100644 index 0000000..366b247 --- /dev/null +++ b/Utilities/cmcurl/lib/slist.c @@ -0,0 +1,146 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "slist.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* returns last node in linked list */ +static struct curl_slist *slist_get_last(struct curl_slist *list) +{ + struct curl_slist *item; + + /* if caller passed us a NULL, return now */ + if(!list) + return NULL; + + /* loop through to find the last item */ + item = list; + while(item->next) { + item = item->next; + } + return item; +} + +/* + * Curl_slist_append_nodup() appends a string to the linked list. Rather than + * copying the string in dynamic storage, it takes its ownership. The string + * should have been malloc()ated. Curl_slist_append_nodup always returns + * the address of the first record, so that you can use this function as an + * initialization function as well as an append function. + * If an error occurs, NULL is returned and the string argument is NOT + * released. + */ +struct curl_slist *Curl_slist_append_nodup(struct curl_slist *list, char *data) +{ + struct curl_slist *last; + struct curl_slist *new_item; + + DEBUGASSERT(data); + + new_item = malloc(sizeof(struct curl_slist)); + if(!new_item) + return NULL; + + new_item->next = NULL; + new_item->data = data; + + /* if this is the first item, then new_item *is* the list */ + if(!list) + return new_item; + + last = slist_get_last(list); + last->next = new_item; + return list; +} + +/* + * curl_slist_append() appends a string to the linked list. It always returns + * the address of the first record, so that you can use this function as an + * initialization function as well as an append function. If you find this + * bothersome, then simply create a separate _init function and call it + * appropriately from within the program. + */ +struct curl_slist *curl_slist_append(struct curl_slist *list, + const char *data) +{ + char *dupdata = strdup(data); + + if(!dupdata) + return NULL; + + list = Curl_slist_append_nodup(list, dupdata); + if(!list) + free(dupdata); + + return list; +} + +/* + * Curl_slist_duplicate() duplicates a linked list. It always returns the + * address of the first record of the cloned list or NULL in case of an + * error (or if the input list was NULL). + */ +struct curl_slist *Curl_slist_duplicate(struct curl_slist *inlist) +{ + struct curl_slist *outlist = NULL; + struct curl_slist *tmp; + + while(inlist) { + tmp = curl_slist_append(outlist, inlist->data); + + if(!tmp) { + curl_slist_free_all(outlist); + return NULL; + } + + outlist = tmp; + inlist = inlist->next; + } + return outlist; +} + +/* be nice and clean up resources */ +void curl_slist_free_all(struct curl_slist *list) +{ + struct curl_slist *next; + struct curl_slist *item; + + if(!list) + return; + + item = list; + do { + next = item->next; + Curl_safefree(item->data); + free(item); + item = next; + } while(next); +} diff --git a/Utilities/cmcurl/lib/slist.h b/Utilities/cmcurl/lib/slist.h new file mode 100644 index 0000000..9561fd0 --- /dev/null +++ b/Utilities/cmcurl/lib/slist.h @@ -0,0 +1,41 @@ +#ifndef HEADER_CURL_SLIST_H +#define HEADER_CURL_SLIST_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Curl_slist_duplicate() duplicates a linked list. It always returns the + * address of the first record of the cloned list or NULL in case of an + * error (or if the input list was NULL). + */ +struct curl_slist *Curl_slist_duplicate(struct curl_slist *inlist); + +/* + * Curl_slist_append_nodup() takes ownership of the given string and appends + * it to the list. + */ +struct curl_slist *Curl_slist_append_nodup(struct curl_slist *list, + char *data); + +#endif /* HEADER_CURL_SLIST_H */ diff --git a/Utilities/cmcurl/lib/smb.c b/Utilities/cmcurl/lib/smb.c new file mode 100644 index 0000000..6c8a47c --- /dev/null +++ b/Utilities/cmcurl/lib/smb.c @@ -0,0 +1,1203 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Bill Nagel <wnagel@tycoint.com>, Exacq Technologies + * + * 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_SMB) && defined(USE_CURL_NTLM_CORE) + +#ifdef _WIN32 +#define getpid GetCurrentProcessId +#endif + +#include "smb.h" +#include "urldata.h" +#include "sendf.h" +#include "multiif.h" +#include "cfilters.h" +#include "connect.h" +#include "progress.h" +#include "transfer.h" +#include "vtls/vtls.h" +#include "curl_ntlm_core.h" +#include "escape.h" +#include "curl_endian.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Definitions for SMB protocol data structures + */ +#if defined(_MSC_VER) || defined(__ILEC400__) +# define PACK +# pragma pack(push) +# pragma pack(1) +#elif defined(__GNUC__) +# define PACK __attribute__((packed)) +#else +# define PACK +#endif + +#define SMB_COM_CLOSE 0x04 +#define SMB_COM_READ_ANDX 0x2e +#define SMB_COM_WRITE_ANDX 0x2f +#define SMB_COM_TREE_DISCONNECT 0x71 +#define SMB_COM_NEGOTIATE 0x72 +#define SMB_COM_SETUP_ANDX 0x73 +#define SMB_COM_TREE_CONNECT_ANDX 0x75 +#define SMB_COM_NT_CREATE_ANDX 0xa2 +#define SMB_COM_NO_ANDX_COMMAND 0xff + +#define SMB_WC_CLOSE 0x03 +#define SMB_WC_READ_ANDX 0x0c +#define SMB_WC_WRITE_ANDX 0x0e +#define SMB_WC_SETUP_ANDX 0x0d +#define SMB_WC_TREE_CONNECT_ANDX 0x04 +#define SMB_WC_NT_CREATE_ANDX 0x18 + +#define SMB_FLAGS_CANONICAL_PATHNAMES 0x10 +#define SMB_FLAGS_CASELESS_PATHNAMES 0x08 +#define SMB_FLAGS2_UNICODE_STRINGS 0x8000 +#define SMB_FLAGS2_IS_LONG_NAME 0x0040 +#define SMB_FLAGS2_KNOWS_LONG_NAME 0x0001 + +#define SMB_CAP_LARGE_FILES 0x08 +#define SMB_GENERIC_WRITE 0x40000000 +#define SMB_GENERIC_READ 0x80000000 +#define SMB_FILE_SHARE_ALL 0x07 +#define SMB_FILE_OPEN 0x01 +#define SMB_FILE_OVERWRITE_IF 0x05 + +#define SMB_ERR_NOACCESS 0x00050001 + +struct smb_header { + unsigned char nbt_type; + unsigned char nbt_flags; + unsigned short nbt_length; + unsigned char magic[4]; + unsigned char command; + unsigned int status; + unsigned char flags; + unsigned short flags2; + unsigned short pid_high; + unsigned char signature[8]; + unsigned short pad; + unsigned short tid; + unsigned short pid; + unsigned short uid; + unsigned short mid; +} PACK; + +struct smb_negotiate_response { + struct smb_header h; + unsigned char word_count; + unsigned short dialect_index; + unsigned char security_mode; + unsigned short max_mpx_count; + unsigned short max_number_vcs; + unsigned int max_buffer_size; + unsigned int max_raw_size; + unsigned int session_key; + unsigned int capabilities; + unsigned int system_time_low; + unsigned int system_time_high; + unsigned short server_time_zone; + unsigned char encryption_key_length; + unsigned short byte_count; + char bytes[1]; +} PACK; + +struct andx { + unsigned char command; + unsigned char pad; + unsigned short offset; +} PACK; + +struct smb_setup { + unsigned char word_count; + struct andx andx; + unsigned short max_buffer_size; + unsigned short max_mpx_count; + unsigned short vc_number; + unsigned int session_key; + unsigned short lengths[2]; + unsigned int pad; + unsigned int capabilities; + unsigned short byte_count; + char bytes[1024]; +} PACK; + +struct smb_tree_connect { + unsigned char word_count; + struct andx andx; + unsigned short flags; + unsigned short pw_len; + unsigned short byte_count; + char bytes[1024]; +} PACK; + +struct smb_nt_create { + unsigned char word_count; + struct andx andx; + unsigned char pad; + unsigned short name_length; + unsigned int flags; + unsigned int root_fid; + unsigned int access; + curl_off_t allocation_size; + unsigned int ext_file_attributes; + unsigned int share_access; + unsigned int create_disposition; + unsigned int create_options; + unsigned int impersonation_level; + unsigned char security_flags; + unsigned short byte_count; + char bytes[1024]; +} PACK; + +struct smb_nt_create_response { + struct smb_header h; + unsigned char word_count; + struct andx andx; + unsigned char op_lock_level; + unsigned short fid; + unsigned int create_disposition; + + curl_off_t create_time; + curl_off_t last_access_time; + curl_off_t last_write_time; + curl_off_t last_change_time; + unsigned int ext_file_attributes; + curl_off_t allocation_size; + curl_off_t end_of_file; +} PACK; + +struct smb_read { + unsigned char word_count; + struct andx andx; + unsigned short fid; + unsigned int offset; + unsigned short max_bytes; + unsigned short min_bytes; + unsigned int timeout; + unsigned short remaining; + unsigned int offset_high; + unsigned short byte_count; +} PACK; + +struct smb_write { + struct smb_header h; + unsigned char word_count; + struct andx andx; + unsigned short fid; + unsigned int offset; + unsigned int timeout; + unsigned short write_mode; + unsigned short remaining; + unsigned short pad; + unsigned short data_length; + unsigned short data_offset; + unsigned int offset_high; + unsigned short byte_count; + unsigned char pad2; +} PACK; + +struct smb_close { + unsigned char word_count; + unsigned short fid; + unsigned int last_mtime; + unsigned short byte_count; +} PACK; + +struct smb_tree_disconnect { + unsigned char word_count; + unsigned short byte_count; +} PACK; + +#if defined(_MSC_VER) || defined(__ILEC400__) +# pragma pack(pop) +#endif + +/* Local API functions */ +static CURLcode smb_setup_connection(struct Curl_easy *data, + struct connectdata *conn); +static CURLcode smb_connect(struct Curl_easy *data, bool *done); +static CURLcode smb_connection_state(struct Curl_easy *data, bool *done); +static CURLcode smb_do(struct Curl_easy *data, bool *done); +static CURLcode smb_request_state(struct Curl_easy *data, bool *done); +static CURLcode smb_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead); +static int smb_getsock(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *socks); +static CURLcode smb_parse_url_path(struct Curl_easy *data, + struct connectdata *conn); + +/* + * SMB handler interface + */ +const struct Curl_handler Curl_handler_smb = { + "SMB", /* scheme */ + smb_setup_connection, /* setup_connection */ + smb_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + smb_connect, /* connect_it */ + smb_connection_state, /* connecting */ + smb_request_state, /* doing */ + smb_getsock, /* proto_getsock */ + smb_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + smb_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_SMB, /* defport */ + CURLPROTO_SMB, /* protocol */ + CURLPROTO_SMB, /* family */ + PROTOPT_NONE /* flags */ +}; + +#ifdef USE_SSL +/* + * SMBS handler interface + */ +const struct Curl_handler Curl_handler_smbs = { + "SMBS", /* scheme */ + smb_setup_connection, /* setup_connection */ + smb_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + smb_connect, /* connect_it */ + smb_connection_state, /* connecting */ + smb_request_state, /* doing */ + smb_getsock, /* proto_getsock */ + smb_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + smb_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_SMBS, /* defport */ + CURLPROTO_SMBS, /* protocol */ + CURLPROTO_SMB, /* family */ + PROTOPT_SSL /* flags */ +}; +#endif + +#define MAX_PAYLOAD_SIZE 0x8000 +#define MAX_MESSAGE_SIZE (MAX_PAYLOAD_SIZE + 0x1000) +#define CLIENTNAME "curl" +#define SERVICENAME "?????" + +/* Append a string to an SMB message */ +#define MSGCAT(str) \ + do { \ + strcpy(p, (str)); \ + p += strlen(str); \ + } while(0) + +/* Append a null-terminated string to an SMB message */ +#define MSGCATNULL(str) \ + do { \ + strcpy(p, (str)); \ + p += strlen(str) + 1; \ + } while(0) + +/* SMB is mostly little endian */ +#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ + defined(__OS400__) +static unsigned short smb_swap16(unsigned short x) +{ + return (unsigned short) ((x << 8) | ((x >> 8) & 0xff)); +} + +static unsigned int smb_swap32(unsigned int x) +{ + return (x << 24) | ((x << 8) & 0xff0000) | ((x >> 8) & 0xff00) | + ((x >> 24) & 0xff); +} + +static curl_off_t smb_swap64(curl_off_t x) +{ + return ((curl_off_t) smb_swap32((unsigned int) x) << 32) | + smb_swap32((unsigned int) (x >> 32)); +} + +#else +# define smb_swap16(x) (x) +# define smb_swap32(x) (x) +# define smb_swap64(x) (x) +#endif + +/* SMB request state */ +enum smb_req_state { + SMB_REQUESTING, + SMB_TREE_CONNECT, + SMB_OPEN, + SMB_DOWNLOAD, + SMB_UPLOAD, + SMB_CLOSE, + SMB_TREE_DISCONNECT, + SMB_DONE +}; + +/* SMB request data */ +struct smb_request { + enum smb_req_state state; + char *path; + unsigned short tid; /* Even if we connect to the same tree as another */ + unsigned short fid; /* request, the tid will be different */ + CURLcode result; +}; + +static void conn_state(struct Curl_easy *data, enum smb_conn_state newstate) +{ + struct smb_conn *smbc = &data->conn->proto.smbc; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* For debug purposes */ + static const char * const names[] = { + "SMB_NOT_CONNECTED", + "SMB_CONNECTING", + "SMB_NEGOTIATE", + "SMB_SETUP", + "SMB_CONNECTED", + /* LAST */ + }; + + if(smbc->state != newstate) + infof(data, "SMB conn %p state change from %s to %s", + (void *)smbc, names[smbc->state], names[newstate]); +#endif + + smbc->state = newstate; +} + +static void request_state(struct Curl_easy *data, + enum smb_req_state newstate) +{ + struct smb_request *req = data->req.p.smb; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* For debug purposes */ + static const char * const names[] = { + "SMB_REQUESTING", + "SMB_TREE_CONNECT", + "SMB_OPEN", + "SMB_DOWNLOAD", + "SMB_UPLOAD", + "SMB_CLOSE", + "SMB_TREE_DISCONNECT", + "SMB_DONE", + /* LAST */ + }; + + if(req->state != newstate) + infof(data, "SMB request %p state change from %s to %s", + (void *)req, names[req->state], names[newstate]); +#endif + + req->state = newstate; +} + +/* this should setup things in the connection, not in the easy + handle */ +static CURLcode smb_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + struct smb_request *req; + + /* Initialize the request state */ + data->req.p.smb = req = calloc(1, sizeof(struct smb_request)); + if(!req) + return CURLE_OUT_OF_MEMORY; + + /* Parse the URL path */ + return smb_parse_url_path(data, conn); +} + +static CURLcode smb_connect(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + char *slash; + + (void) done; + + /* Check we have a username and password to authenticate with */ + if(!data->state.aptr.user) + return CURLE_LOGIN_DENIED; + + /* Initialize the connection state */ + smbc->state = SMB_CONNECTING; + smbc->recv_buf = malloc(MAX_MESSAGE_SIZE); + if(!smbc->recv_buf) + return CURLE_OUT_OF_MEMORY; + + /* Multiple requests are allowed with this connection */ + connkeep(conn, "SMB default"); + + /* Parse the username, domain, and password */ + slash = strchr(conn->user, '/'); + if(!slash) + slash = strchr(conn->user, '\\'); + + if(slash) { + smbc->user = slash + 1; + smbc->domain = strdup(conn->user); + if(!smbc->domain) + return CURLE_OUT_OF_MEMORY; + smbc->domain[slash - conn->user] = 0; + } + else { + smbc->user = conn->user; + smbc->domain = strdup(conn->host.name); + if(!smbc->domain) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +static CURLcode smb_recv_message(struct Curl_easy *data, void **msg) +{ + struct connectdata *conn = data->conn; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + struct smb_conn *smbc = &conn->proto.smbc; + char *buf = smbc->recv_buf; + ssize_t bytes_read; + size_t nbt_size; + size_t msg_size; + size_t len = MAX_MESSAGE_SIZE - smbc->got; + CURLcode result; + + result = Curl_read(data, sockfd, buf + smbc->got, len, &bytes_read); + if(result) + return result; + + if(!bytes_read) + return CURLE_OK; + + smbc->got += bytes_read; + + /* Check for a 32-bit nbt header */ + if(smbc->got < sizeof(unsigned int)) + return CURLE_OK; + + nbt_size = Curl_read16_be((const unsigned char *) + (buf + sizeof(unsigned short))) + + sizeof(unsigned int); + if(smbc->got < nbt_size) + return CURLE_OK; + + msg_size = sizeof(struct smb_header); + if(nbt_size >= msg_size + 1) { + /* Add the word count */ + msg_size += 1 + ((unsigned char) buf[msg_size]) * sizeof(unsigned short); + if(nbt_size >= msg_size + sizeof(unsigned short)) { + /* Add the byte count */ + msg_size += sizeof(unsigned short) + + Curl_read16_le((const unsigned char *)&buf[msg_size]); + if(nbt_size < msg_size) + return CURLE_READ_ERROR; + } + } + + *msg = buf; + + return CURLE_OK; +} + +static void smb_pop_message(struct connectdata *conn) +{ + struct smb_conn *smbc = &conn->proto.smbc; + + smbc->got = 0; +} + +static void smb_format_message(struct Curl_easy *data, struct smb_header *h, + unsigned char cmd, size_t len) +{ + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + struct smb_request *req = data->req.p.smb; + unsigned int pid; + + memset(h, 0, sizeof(*h)); + h->nbt_length = htons((unsigned short) (sizeof(*h) - sizeof(unsigned int) + + len)); + memcpy((char *)h->magic, "\xffSMB", 4); + h->command = cmd; + h->flags = SMB_FLAGS_CANONICAL_PATHNAMES | SMB_FLAGS_CASELESS_PATHNAMES; + h->flags2 = smb_swap16(SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_KNOWS_LONG_NAME); + h->uid = smb_swap16(smbc->uid); + h->tid = smb_swap16(req->tid); + pid = getpid(); + h->pid_high = smb_swap16((unsigned short)(pid >> 16)); + h->pid = smb_swap16((unsigned short) pid); +} + +static CURLcode smb_send(struct Curl_easy *data, ssize_t len, + size_t upload_size) +{ + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + ssize_t bytes_written; + CURLcode result; + + result = Curl_nwrite(data, FIRSTSOCKET, data->state.ulbuf, + len, &bytes_written); + if(result) + return result; + + if(bytes_written != len) { + smbc->send_size = len; + smbc->sent = bytes_written; + } + + smbc->upload_size = upload_size; + + return CURLE_OK; +} + +static CURLcode smb_flush(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + ssize_t bytes_written; + ssize_t len = smbc->send_size - smbc->sent; + CURLcode result; + + if(!smbc->send_size) + return CURLE_OK; + + result = Curl_nwrite(data, FIRSTSOCKET, + data->state.ulbuf + smbc->sent, + len, &bytes_written); + if(result) + return result; + + if(bytes_written != len) + smbc->sent += bytes_written; + else + smbc->send_size = 0; + + return CURLE_OK; +} + +static CURLcode smb_send_message(struct Curl_easy *data, unsigned char cmd, + const void *msg, size_t msg_len) +{ + CURLcode result = Curl_get_upload_buffer(data); + if(result) + return result; + smb_format_message(data, (struct smb_header *)data->state.ulbuf, + cmd, msg_len); + memcpy(data->state.ulbuf + sizeof(struct smb_header), + msg, msg_len); + + return smb_send(data, sizeof(struct smb_header) + msg_len, 0); +} + +static CURLcode smb_send_negotiate(struct Curl_easy *data) +{ + const char *msg = "\x00\x0c\x00\x02NT LM 0.12"; + + return smb_send_message(data, SMB_COM_NEGOTIATE, msg, 15); +} + +static CURLcode smb_send_setup(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + struct smb_setup msg; + char *p = msg.bytes; + unsigned char lm_hash[21]; + unsigned char lm[24]; + unsigned char nt_hash[21]; + unsigned char nt[24]; + + size_t byte_count = sizeof(lm) + sizeof(nt); + byte_count += strlen(smbc->user) + strlen(smbc->domain); + byte_count += strlen(OS) + strlen(CLIENTNAME) + 4; /* 4 null chars */ + if(byte_count > sizeof(msg.bytes)) + return CURLE_FILESIZE_EXCEEDED; + + Curl_ntlm_core_mk_lm_hash(conn->passwd, lm_hash); + Curl_ntlm_core_lm_resp(lm_hash, smbc->challenge, lm); + Curl_ntlm_core_mk_nt_hash(conn->passwd, nt_hash); + Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt); + + memset(&msg, 0, sizeof(msg)); + msg.word_count = SMB_WC_SETUP_ANDX; + msg.andx.command = SMB_COM_NO_ANDX_COMMAND; + msg.max_buffer_size = smb_swap16(MAX_MESSAGE_SIZE); + msg.max_mpx_count = smb_swap16(1); + msg.vc_number = smb_swap16(1); + msg.session_key = smb_swap32(smbc->session_key); + msg.capabilities = smb_swap32(SMB_CAP_LARGE_FILES); + msg.lengths[0] = smb_swap16(sizeof(lm)); + msg.lengths[1] = smb_swap16(sizeof(nt)); + memcpy(p, lm, sizeof(lm)); + p += sizeof(lm); + memcpy(p, nt, sizeof(nt)); + p += sizeof(nt); + MSGCATNULL(smbc->user); + MSGCATNULL(smbc->domain); + MSGCATNULL(OS); + MSGCATNULL(CLIENTNAME); + byte_count = p - msg.bytes; + msg.byte_count = smb_swap16((unsigned short)byte_count); + + return smb_send_message(data, SMB_COM_SETUP_ANDX, &msg, + sizeof(msg) - sizeof(msg.bytes) + byte_count); +} + +static CURLcode smb_send_tree_connect(struct Curl_easy *data) +{ + struct smb_tree_connect msg; + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + char *p = msg.bytes; + + size_t byte_count = strlen(conn->host.name) + strlen(smbc->share); + byte_count += strlen(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ + if(byte_count > sizeof(msg.bytes)) + return CURLE_FILESIZE_EXCEEDED; + + memset(&msg, 0, sizeof(msg)); + msg.word_count = SMB_WC_TREE_CONNECT_ANDX; + msg.andx.command = SMB_COM_NO_ANDX_COMMAND; + msg.pw_len = 0; + MSGCAT("\\\\"); + MSGCAT(conn->host.name); + MSGCAT("\\"); + MSGCATNULL(smbc->share); + MSGCATNULL(SERVICENAME); /* Match any type of service */ + byte_count = p - msg.bytes; + msg.byte_count = smb_swap16((unsigned short)byte_count); + + return smb_send_message(data, SMB_COM_TREE_CONNECT_ANDX, &msg, + sizeof(msg) - sizeof(msg.bytes) + byte_count); +} + +static CURLcode smb_send_open(struct Curl_easy *data) +{ + struct smb_request *req = data->req.p.smb; + struct smb_nt_create msg; + size_t byte_count; + + if((strlen(req->path) + 1) > sizeof(msg.bytes)) + return CURLE_FILESIZE_EXCEEDED; + + memset(&msg, 0, sizeof(msg)); + msg.word_count = SMB_WC_NT_CREATE_ANDX; + msg.andx.command = SMB_COM_NO_ANDX_COMMAND; + byte_count = strlen(req->path); + msg.name_length = smb_swap16((unsigned short)byte_count); + msg.share_access = smb_swap32(SMB_FILE_SHARE_ALL); + if(data->state.upload) { + msg.access = smb_swap32(SMB_GENERIC_READ | SMB_GENERIC_WRITE); + msg.create_disposition = smb_swap32(SMB_FILE_OVERWRITE_IF); + } + else { + msg.access = smb_swap32(SMB_GENERIC_READ); + msg.create_disposition = smb_swap32(SMB_FILE_OPEN); + } + msg.byte_count = smb_swap16((unsigned short) ++byte_count); + strcpy(msg.bytes, req->path); + + return smb_send_message(data, SMB_COM_NT_CREATE_ANDX, &msg, + sizeof(msg) - sizeof(msg.bytes) + byte_count); +} + +static CURLcode smb_send_close(struct Curl_easy *data) +{ + struct smb_request *req = data->req.p.smb; + struct smb_close msg; + + memset(&msg, 0, sizeof(msg)); + msg.word_count = SMB_WC_CLOSE; + msg.fid = smb_swap16(req->fid); + + return smb_send_message(data, SMB_COM_CLOSE, &msg, sizeof(msg)); +} + +static CURLcode smb_send_tree_disconnect(struct Curl_easy *data) +{ + struct smb_tree_disconnect msg; + + memset(&msg, 0, sizeof(msg)); + + return smb_send_message(data, SMB_COM_TREE_DISCONNECT, &msg, sizeof(msg)); +} + +static CURLcode smb_send_read(struct Curl_easy *data) +{ + struct smb_request *req = data->req.p.smb; + curl_off_t offset = data->req.offset; + struct smb_read msg; + + memset(&msg, 0, sizeof(msg)); + msg.word_count = SMB_WC_READ_ANDX; + msg.andx.command = SMB_COM_NO_ANDX_COMMAND; + msg.fid = smb_swap16(req->fid); + msg.offset = smb_swap32((unsigned int) offset); + msg.offset_high = smb_swap32((unsigned int) (offset >> 32)); + msg.min_bytes = smb_swap16(MAX_PAYLOAD_SIZE); + msg.max_bytes = smb_swap16(MAX_PAYLOAD_SIZE); + + return smb_send_message(data, SMB_COM_READ_ANDX, &msg, sizeof(msg)); +} + +static CURLcode smb_send_write(struct Curl_easy *data) +{ + struct smb_write *msg; + struct smb_request *req = data->req.p.smb; + curl_off_t offset = data->req.offset; + curl_off_t upload_size = data->req.size - data->req.bytecount; + CURLcode result = Curl_get_upload_buffer(data); + if(result) + return result; + msg = (struct smb_write *)data->state.ulbuf; + + if(upload_size >= MAX_PAYLOAD_SIZE - 1) /* There is one byte of padding */ + upload_size = MAX_PAYLOAD_SIZE - 1; + + memset(msg, 0, sizeof(*msg)); + msg->word_count = SMB_WC_WRITE_ANDX; + msg->andx.command = SMB_COM_NO_ANDX_COMMAND; + msg->fid = smb_swap16(req->fid); + msg->offset = smb_swap32((unsigned int) offset); + msg->offset_high = smb_swap32((unsigned int) (offset >> 32)); + msg->data_length = smb_swap16((unsigned short) upload_size); + msg->data_offset = smb_swap16(sizeof(*msg) - sizeof(unsigned int)); + msg->byte_count = smb_swap16((unsigned short) (upload_size + 1)); + + smb_format_message(data, &msg->h, SMB_COM_WRITE_ANDX, + sizeof(*msg) - sizeof(msg->h) + (size_t) upload_size); + + return smb_send(data, sizeof(*msg), (size_t) upload_size); +} + +static CURLcode smb_send_and_recv(struct Curl_easy *data, void **msg) +{ + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + CURLcode result; + *msg = NULL; /* if it returns early */ + + /* Check if there is data in the transfer buffer */ + if(!smbc->send_size && smbc->upload_size) { + size_t nread = smbc->upload_size > (size_t)data->set.upload_buffer_size ? + (size_t)data->set.upload_buffer_size : smbc->upload_size; + data->req.upload_fromhere = data->state.ulbuf; + result = Curl_fillreadbuffer(data, nread, &nread); + if(result && result != CURLE_AGAIN) + return result; + if(!nread) + return CURLE_OK; + + smbc->upload_size -= nread; + smbc->send_size = nread; + smbc->sent = 0; + } + + /* Check if there is data to send */ + if(smbc->send_size) { + result = smb_flush(data); + if(result) + return result; + } + + /* Check if there is still data to be sent */ + if(smbc->send_size || smbc->upload_size) + return CURLE_AGAIN; + + return smb_recv_message(data, msg); +} + +static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + struct smb_negotiate_response *nrsp; + struct smb_header *h; + CURLcode result; + void *msg = NULL; + + if(smbc->state == SMB_CONNECTING) { +#ifdef USE_SSL + if((conn->handler->flags & PROTOPT_SSL)) { + bool ssl_done = FALSE; + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssl_done); + if(result && result != CURLE_AGAIN) + return result; + if(!ssl_done) + return CURLE_OK; + } +#endif + + result = smb_send_negotiate(data); + if(result) { + connclose(conn, "SMB: failed to send negotiate message"); + return result; + } + + conn_state(data, SMB_NEGOTIATE); + } + + /* Send the previous message and check for a response */ + result = smb_send_and_recv(data, &msg); + if(result && result != CURLE_AGAIN) { + connclose(conn, "SMB: failed to communicate"); + return result; + } + + if(!msg) + return CURLE_OK; + + h = msg; + + switch(smbc->state) { + case SMB_NEGOTIATE: + if((smbc->got < sizeof(*nrsp) + sizeof(smbc->challenge) - 1) || + h->status) { + connclose(conn, "SMB: negotiation failed"); + return CURLE_COULDNT_CONNECT; + } + nrsp = msg; + memcpy(smbc->challenge, nrsp->bytes, sizeof(smbc->challenge)); + smbc->session_key = smb_swap32(nrsp->session_key); + result = smb_send_setup(data); + if(result) { + connclose(conn, "SMB: failed to send setup message"); + return result; + } + conn_state(data, SMB_SETUP); + break; + + case SMB_SETUP: + if(h->status) { + connclose(conn, "SMB: authentication failed"); + return CURLE_LOGIN_DENIED; + } + smbc->uid = smb_swap16(h->uid); + conn_state(data, SMB_CONNECTED); + *done = true; + break; + + default: + smb_pop_message(conn); + return CURLE_OK; /* ignore */ + } + + smb_pop_message(conn); + + return CURLE_OK; +} + +/* + * Convert a timestamp from the Windows world (100 nsec units from 1 Jan 1601) + * to Posix time. Cap the output to fit within a time_t. + */ +static void get_posix_time(time_t *out, curl_off_t timestamp) +{ + timestamp -= 116444736000000000; + timestamp /= 10000000; +#if SIZEOF_TIME_T < SIZEOF_CURL_OFF_T + if(timestamp > TIME_T_MAX) + *out = TIME_T_MAX; + else if(timestamp < TIME_T_MIN) + *out = TIME_T_MIN; + else +#endif + *out = (time_t) timestamp; +} + +static CURLcode smb_request_state(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + struct smb_request *req = data->req.p.smb; + struct smb_header *h; + struct smb_conn *smbc = &conn->proto.smbc; + enum smb_req_state next_state = SMB_DONE; + unsigned short len; + unsigned short off; + CURLcode result; + void *msg = NULL; + const struct smb_nt_create_response *smb_m; + + if(data->state.upload && (data->state.infilesize < 0)) { + failf(data, "SMB upload needs to know the size up front"); + return CURLE_SEND_ERROR; + } + + /* Start the request */ + if(req->state == SMB_REQUESTING) { + result = smb_send_tree_connect(data); + if(result) { + connclose(conn, "SMB: failed to send tree connect message"); + return result; + } + + request_state(data, SMB_TREE_CONNECT); + } + + /* Send the previous message and check for a response */ + result = smb_send_and_recv(data, &msg); + if(result && result != CURLE_AGAIN) { + connclose(conn, "SMB: failed to communicate"); + return result; + } + + if(!msg) + return CURLE_OK; + + h = msg; + + switch(req->state) { + case SMB_TREE_CONNECT: + if(h->status) { + req->result = CURLE_REMOTE_FILE_NOT_FOUND; + if(h->status == smb_swap32(SMB_ERR_NOACCESS)) + req->result = CURLE_REMOTE_ACCESS_DENIED; + break; + } + req->tid = smb_swap16(h->tid); + next_state = SMB_OPEN; + break; + + case SMB_OPEN: + if(h->status || smbc->got < sizeof(struct smb_nt_create_response)) { + req->result = CURLE_REMOTE_FILE_NOT_FOUND; + if(h->status == smb_swap32(SMB_ERR_NOACCESS)) + req->result = CURLE_REMOTE_ACCESS_DENIED; + next_state = SMB_TREE_DISCONNECT; + break; + } + smb_m = (const struct smb_nt_create_response*) msg; + req->fid = smb_swap16(smb_m->fid); + data->req.offset = 0; + if(data->state.upload) { + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->req.size); + next_state = SMB_UPLOAD; + } + else { + data->req.size = smb_swap64(smb_m->end_of_file); + if(data->req.size < 0) { + req->result = CURLE_WEIRD_SERVER_REPLY; + next_state = SMB_CLOSE; + } + else { + Curl_pgrsSetDownloadSize(data, data->req.size); + if(data->set.get_filetime) + get_posix_time(&data->info.filetime, smb_m->last_change_time); + next_state = SMB_DOWNLOAD; + } + } + break; + + case SMB_DOWNLOAD: + if(h->status || smbc->got < sizeof(struct smb_header) + 14) { + req->result = CURLE_RECV_ERROR; + next_state = SMB_CLOSE; + break; + } + len = Curl_read16_le(((const unsigned char *) msg) + + sizeof(struct smb_header) + 11); + off = Curl_read16_le(((const unsigned char *) msg) + + sizeof(struct smb_header) + 13); + if(len > 0) { + if(off + sizeof(unsigned int) + len > smbc->got) { + failf(data, "Invalid input packet"); + result = CURLE_RECV_ERROR; + } + else + result = Curl_client_write(data, CLIENTWRITE_BODY, + (char *)msg + off + sizeof(unsigned int), + len); + if(result) { + req->result = result; + next_state = SMB_CLOSE; + break; + } + } + data->req.offset += len; + next_state = (len < MAX_PAYLOAD_SIZE) ? SMB_CLOSE : SMB_DOWNLOAD; + break; + + case SMB_UPLOAD: + if(h->status || smbc->got < sizeof(struct smb_header) + 6) { + req->result = CURLE_UPLOAD_FAILED; + next_state = SMB_CLOSE; + break; + } + len = Curl_read16_le(((const unsigned char *) msg) + + sizeof(struct smb_header) + 5); + data->req.bytecount += len; + data->req.offset += len; + Curl_pgrsSetUploadCounter(data, data->req.bytecount); + if(data->req.bytecount >= data->req.size) + next_state = SMB_CLOSE; + else + next_state = SMB_UPLOAD; + break; + + case SMB_CLOSE: + /* We don't care if the close failed, proceed to tree disconnect anyway */ + next_state = SMB_TREE_DISCONNECT; + break; + + case SMB_TREE_DISCONNECT: + next_state = SMB_DONE; + break; + + default: + smb_pop_message(conn); + return CURLE_OK; /* ignore */ + } + + smb_pop_message(conn); + + switch(next_state) { + case SMB_OPEN: + result = smb_send_open(data); + break; + + case SMB_DOWNLOAD: + result = smb_send_read(data); + break; + + case SMB_UPLOAD: + result = smb_send_write(data); + break; + + case SMB_CLOSE: + result = smb_send_close(data); + break; + + case SMB_TREE_DISCONNECT: + result = smb_send_tree_disconnect(data); + break; + + case SMB_DONE: + result = req->result; + *done = true; + break; + + default: + break; + } + + if(result) { + connclose(conn, "SMB: failed to send message"); + return result; + } + + request_state(data, next_state); + + return CURLE_OK; +} + +static CURLcode smb_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead) +{ + struct smb_conn *smbc = &conn->proto.smbc; + (void) dead; + (void) data; + Curl_safefree(smbc->share); + Curl_safefree(smbc->domain); + Curl_safefree(smbc->recv_buf); + return CURLE_OK; +} + +static int smb_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) +{ + (void)data; + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_READSOCK(0) | GETSOCK_WRITESOCK(0); +} + +static CURLcode smb_do(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + + *done = FALSE; + if(smbc->share) { + return CURLE_OK; + } + return CURLE_URL_MALFORMAT; +} + +static CURLcode smb_parse_url_path(struct Curl_easy *data, + struct connectdata *conn) +{ + struct smb_request *req = data->req.p.smb; + struct smb_conn *smbc = &conn->proto.smbc; + char *path; + char *slash; + + /* URL decode the path */ + CURLcode result = Curl_urldecode(data->state.up.path, 0, &path, NULL, + REJECT_CTRL); + if(result) + return result; + + /* Parse the path for the share */ + smbc->share = strdup((*path == '/' || *path == '\\') ? path + 1 : path); + free(path); + if(!smbc->share) + return CURLE_OUT_OF_MEMORY; + + slash = strchr(smbc->share, '/'); + if(!slash) + slash = strchr(smbc->share, '\\'); + + /* The share must be present */ + if(!slash) { + Curl_safefree(smbc->share); + failf(data, "missing share in URL path for SMB"); + return CURLE_URL_MALFORMAT; + } + + /* Parse the path for the file path converting any forward slashes into + backslashes */ + *slash++ = 0; + req->path = slash; + + for(; *slash; slash++) { + if(*slash == '/') + *slash = '\\'; + } + return CURLE_OK; +} + +#endif /* CURL_DISABLE_SMB && USE_CURL_NTLM_CORE && + SIZEOF_CURL_OFF_T > 4 */ diff --git a/Utilities/cmcurl/lib/smb.h b/Utilities/cmcurl/lib/smb.h new file mode 100644 index 0000000..437f4a5 --- /dev/null +++ b/Utilities/cmcurl/lib/smb.h @@ -0,0 +1,60 @@ +#ifndef HEADER_CURL_SMB_H +#define HEADER_CURL_SMB_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Bill Nagel <wnagel@tycoint.com>, Exacq Technologies + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +enum smb_conn_state { + SMB_NOT_CONNECTED = 0, + SMB_CONNECTING, + SMB_NEGOTIATE, + SMB_SETUP, + SMB_CONNECTED +}; + +struct smb_conn { + enum smb_conn_state state; + char *user; + char *domain; + char *share; + unsigned char challenge[8]; + unsigned int session_key; + unsigned short uid; + char *recv_buf; + size_t upload_size; + size_t send_size; + size_t sent; + size_t got; +}; + +#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) && \ + (SIZEOF_CURL_OFF_T > 4) + +extern const struct Curl_handler Curl_handler_smb; +extern const struct Curl_handler Curl_handler_smbs; + +#endif /* CURL_DISABLE_SMB && USE_CURL_NTLM_CORE && + SIZEOF_CURL_OFF_T > 4 */ + +#endif /* HEADER_CURL_SMB_H */ diff --git a/Utilities/cmcurl/lib/smtp.c b/Utilities/cmcurl/lib/smtp.c new file mode 100644 index 0000000..65fbc5b --- /dev/null +++ b/Utilities/cmcurl/lib/smtp.c @@ -0,0 +1,1929 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC1870 SMTP Service Extension for Message Size + * RFC2195 CRAM-MD5 authentication + * RFC2831 DIGEST-MD5 authentication + * RFC3207 SMTP over TLS + * RFC4422 Simple Authentication and Security Layer (SASL) + * RFC4616 PLAIN authentication + * RFC4752 The Kerberos V5 ("GSSAPI") SASL Mechanism + * RFC4954 SMTP Authentication + * RFC5321 SMTP protocol + * RFC5890 Internationalized Domain Names for Applications (IDNA) + * RFC6531 SMTP Extension for Internationalized Email + * RFC6532 Internationalized Email Headers + * RFC6749 OAuth 2.0 Authorization Framework + * RFC8314 Use of TLS for Email Submission and Access + * Draft SMTP URL Interface <draft-earhart-url-smtp-00.txt> + * Draft LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt> + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_SMTP + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include <curl/curl.h> +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "progress.h" +#include "transfer.h" +#include "escape.h" +#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "mime.h" +#include "socks.h" +#include "smtp.h" +#include "strtoofft.h" +#include "strcase.h" +#include "vtls/vtls.h" +#include "cfilters.h" +#include "connect.h" +#include "select.h" +#include "multiif.h" +#include "url.h" +#include "curl_gethostname.h" +#include "bufref.h" +#include "curl_sasl.h" +#include "warnless.h" +#include "idn.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* Local API functions */ +static CURLcode smtp_regular_transfer(struct Curl_easy *data, bool *done); +static CURLcode smtp_do(struct Curl_easy *data, bool *done); +static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, + bool premature); +static CURLcode smtp_connect(struct Curl_easy *data, bool *done); +static CURLcode smtp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead); +static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done); +static int smtp_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); +static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done); +static CURLcode smtp_setup_connection(struct Curl_easy *data, + struct connectdata *conn); +static CURLcode smtp_parse_url_options(struct connectdata *conn); +static CURLcode smtp_parse_url_path(struct Curl_easy *data); +static CURLcode smtp_parse_custom_request(struct Curl_easy *data); +static CURLcode smtp_parse_address(const char *fqma, + char **address, struct hostname *host); +static CURLcode smtp_perform_auth(struct Curl_easy *data, const char *mech, + const struct bufref *initresp); +static CURLcode smtp_continue_auth(struct Curl_easy *data, const char *mech, + const struct bufref *resp); +static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech); +static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out); + +/* + * SMTP protocol handler. + */ + +const struct Curl_handler Curl_handler_smtp = { + "SMTP", /* scheme */ + smtp_setup_connection, /* setup_connection */ + smtp_do, /* do_it */ + smtp_done, /* done */ + ZERO_NULL, /* do_more */ + smtp_connect, /* connect_it */ + smtp_multi_statemach, /* connecting */ + smtp_doing, /* doing */ + smtp_getsock, /* proto_getsock */ + smtp_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + smtp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_SMTP, /* defport */ + CURLPROTO_SMTP, /* protocol */ + CURLPROTO_SMTP, /* family */ + PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */ + PROTOPT_URLOPTIONS +}; + +#ifdef USE_SSL +/* + * SMTPS protocol handler. + */ + +const struct Curl_handler Curl_handler_smtps = { + "SMTPS", /* scheme */ + smtp_setup_connection, /* setup_connection */ + smtp_do, /* do_it */ + smtp_done, /* done */ + ZERO_NULL, /* do_more */ + smtp_connect, /* connect_it */ + smtp_multi_statemach, /* connecting */ + smtp_doing, /* doing */ + smtp_getsock, /* proto_getsock */ + smtp_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + smtp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_SMTPS, /* defport */ + CURLPROTO_SMTPS, /* protocol */ + CURLPROTO_SMTP, /* family */ + PROTOPT_CLOSEACTION | PROTOPT_SSL + | PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS /* flags */ +}; +#endif + +/* SASL parameters for the smtp protocol */ +static const struct SASLproto saslsmtp = { + "smtp", /* The service name */ + smtp_perform_auth, /* Send authentication command */ + 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 */ + 334, /* Code received when continuation is expected */ + 235, /* Code to receive upon authentication success */ + SASL_AUTH_DEFAULT, /* Default mechanisms */ + SASL_FLAG_BASE64 /* Configuration flags */ +}; + +#ifdef USE_SSL +static void smtp_to_smtps(struct connectdata *conn) +{ + /* Change the connection handler */ + conn->handler = &Curl_handler_smtps; + + /* Set the connection's upgraded to TLS flag */ + conn->bits.tls_upgraded = TRUE; +} +#else +#define smtp_to_smtps(x) Curl_nop_stmt +#endif + +/*********************************************************************** + * + * smtp_endofresp() + * + * Checks for an ending SMTP status code at the start of the given string, but + * also detects various capabilities from the EHLO response including the + * supported authentication mechanisms. + */ +static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn, + char *line, size_t len, int *resp) +{ + struct smtp_conn *smtpc = &conn->proto.smtpc; + bool result = FALSE; + (void)data; + + /* Nothing for us */ + if(len < 4 || !ISDIGIT(line[0]) || !ISDIGIT(line[1]) || !ISDIGIT(line[2])) + return FALSE; + + /* Do we have a command response? This should be the response code followed + by a space and optionally some text as per RFC-5321 and as outlined in + Section 4. Examples of RFC-4954 but some email servers ignore this and + only send the response code instead as per Section 4.2. */ + if(line[3] == ' ' || len == 5) { + char tmpline[6]; + + result = TRUE; + memset(tmpline, '\0', sizeof(tmpline)); + memcpy(tmpline, line, (len == 5 ? 5 : 3)); + *resp = curlx_sltosi(strtol(tmpline, NULL, 10)); + + /* Make sure real server never sends internal value */ + if(*resp == 1) + *resp = 0; + } + /* Do we have a multiline (continuation) response? */ + else if(line[3] == '-' && + (smtpc->state == SMTP_EHLO || smtpc->state == SMTP_COMMAND)) { + result = TRUE; + *resp = 1; /* Internal response code */ + } + + return result; +} + +/*********************************************************************** + * + * smtp_get_message() + * + * Gets the authentication message from the response buffer. + */ +static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out) +{ + char *message = data->state.buffer; + size_t len = strlen(message); + + if(len > 4) { + /* Find the start of the message */ + len -= 4; + for(message += 4; *message == ' ' || *message == '\t'; message++, len--) + ; + + /* Find the end of the message */ + while(len--) + if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' && + message[len] != '\t') + break; + + /* Terminate the message */ + message[++len] = '\0'; + Curl_bufref_set(out, message, len, NULL); + } + else + /* junk input => zero length output */ + Curl_bufref_set(out, "", 0, NULL); + + return CURLE_OK; +} + +/*********************************************************************** + * + * smtp_state() + * + * This is the ONLY way to change SMTP state! + */ +static void smtp_state(struct Curl_easy *data, smtpstate newstate) +{ + struct smtp_conn *smtpc = &data->conn->proto.smtpc; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[] = { + "STOP", + "SERVERGREET", + "EHLO", + "HELO", + "STARTTLS", + "UPGRADETLS", + "AUTH", + "COMMAND", + "MAIL", + "RCPT", + "DATA", + "POSTDATA", + "QUIT", + /* LAST */ + }; + + if(smtpc->state != newstate) + infof(data, "SMTP %p state change from %s to %s", + (void *)smtpc, names[smtpc->state], names[newstate]); +#endif + + smtpc->state = newstate; +} + +/*********************************************************************** + * + * smtp_perform_ehlo() + * + * Sends the EHLO command to not only initialise communication with the ESMTP + * server but to also obtain a list of server side supported capabilities. + */ +static CURLcode smtp_perform_ehlo(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct smtp_conn *smtpc = &conn->proto.smtpc; + + smtpc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanism yet */ + smtpc->sasl.authused = SASL_AUTH_NONE; /* Clear the authentication mechanism + used for esmtp connections */ + smtpc->tls_supported = FALSE; /* Clear the TLS capability */ + smtpc->auth_supported = FALSE; /* Clear the AUTH capability */ + + /* Send the EHLO command */ + result = Curl_pp_sendf(data, &smtpc->pp, "EHLO %s", smtpc->domain); + + if(!result) + smtp_state(data, SMTP_EHLO); + + return result; +} + +/*********************************************************************** + * + * smtp_perform_helo() + * + * Sends the HELO command to initialise communication with the SMTP server. + */ +static CURLcode smtp_perform_helo(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct smtp_conn *smtpc = &conn->proto.smtpc; + + smtpc->sasl.authused = SASL_AUTH_NONE; /* No authentication mechanism used + in smtp connections */ + + /* Send the HELO command */ + result = Curl_pp_sendf(data, &smtpc->pp, "HELO %s", smtpc->domain); + + if(!result) + smtp_state(data, SMTP_HELO); + + return result; +} + +/*********************************************************************** + * + * smtp_perform_starttls() + * + * Sends the STLS command to start the upgrade to TLS. + */ +static CURLcode smtp_perform_starttls(struct Curl_easy *data, + struct connectdata *conn) +{ + /* Send the STARTTLS command */ + CURLcode result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, + "%s", "STARTTLS"); + + if(!result) + smtp_state(data, SMTP_STARTTLS); + + return result; +} + +/*********************************************************************** + * + * smtp_perform_upgrade_tls() + * + * Performs the upgrade to TLS. + */ +static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data) +{ + /* Start the SSL connection */ + struct connectdata *conn = data->conn; + struct smtp_conn *smtpc = &conn->proto.smtpc; + CURLcode result; + bool ssldone = FALSE; + + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + if(result) + goto out; + } + + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + if(!result) { + smtpc->ssldone = ssldone; + if(smtpc->state != SMTP_UPGRADETLS) + smtp_state(data, SMTP_UPGRADETLS); + + if(smtpc->ssldone) { + smtp_to_smtps(conn); + result = smtp_perform_ehlo(data); + } + } +out: + return result; +} + +/*********************************************************************** + * + * smtp_perform_auth() + * + * Sends an AUTH command allowing the client to login with the given SASL + * authentication mechanism. + */ +static CURLcode smtp_perform_auth(struct Curl_easy *data, + const char *mech, + const struct bufref *initresp) +{ + CURLcode result = CURLE_OK; + struct smtp_conn *smtpc = &data->conn->proto.smtpc; + const char *ir = (const char *) Curl_bufref_ptr(initresp); + + if(ir) { /* AUTH <mech> ...<crlf> */ + /* Send the AUTH command with the initial response */ + result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", mech, ir); + } + else { + /* Send the AUTH command */ + result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s", mech); + } + + return result; +} + +/*********************************************************************** + * + * smtp_continue_auth() + * + * Sends SASL continuation data. + */ +static CURLcode smtp_continue_auth(struct Curl_easy *data, + const char *mech, + const struct bufref *resp) +{ + struct smtp_conn *smtpc = &data->conn->proto.smtpc; + + (void)mech; + + return Curl_pp_sendf(data, &smtpc->pp, + "%s", (const char *) Curl_bufref_ptr(resp)); +} + +/*********************************************************************** + * + * smtp_cancel_auth() + * + * Sends SASL cancellation. + */ +static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech) +{ + struct smtp_conn *smtpc = &data->conn->proto.smtpc; + + (void)mech; + + return Curl_pp_sendf(data, &smtpc->pp, "*"); +} + +/*********************************************************************** + * + * smtp_perform_authentication() + * + * Initiates the authentication sequence, with the appropriate SASL + * authentication mechanism. + */ +static CURLcode smtp_perform_authentication(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct smtp_conn *smtpc = &conn->proto.smtpc; + saslprogress progress; + + /* Check we have enough data to authenticate with, and the + server supports authentication, and end the connect phase if not */ + if(!smtpc->auth_supported || + !Curl_sasl_can_authenticate(&smtpc->sasl, data)) { + smtp_state(data, SMTP_STOP); + return result; + } + + /* Calculate the SASL login details */ + result = Curl_sasl_start(&smtpc->sasl, data, FALSE, &progress); + + if(!result) { + if(progress == SASL_INPROGRESS) + smtp_state(data, SMTP_AUTH); + else { + /* Other mechanisms not supported */ + infof(data, "No known authentication mechanisms supported"); + result = CURLE_LOGIN_DENIED; + } + } + + return result; +} + +/*********************************************************************** + * + * smtp_perform_command() + * + * Sends a SMTP based command. + */ +static CURLcode smtp_perform_command(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct SMTP *smtp = data->req.p.smtp; + + if(smtp->rcpt) { + /* We notify the server we are sending UTF-8 data if a) it supports the + SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in + either the local address or host name parts. This is regardless of + whether the host name is encoded using IDN ACE */ + bool utf8 = FALSE; + + if((!smtp->custom) || (!smtp->custom[0])) { + char *address = NULL; + struct hostname host = { NULL, NULL, NULL, NULL }; + + /* Parse the mailbox to verify into the local address and host name + parts, converting the host name to an IDN A-label if necessary */ + result = smtp_parse_address(smtp->rcpt->data, + &address, &host); + if(result) + return result; + + /* Establish whether we should report SMTPUTF8 to the server for this + mailbox as per RFC-6531 sect. 3.1 point 6 */ + utf8 = (conn->proto.smtpc.utf8_supported) && + ((host.encalloc) || (!Curl_is_ASCII_name(address)) || + (!Curl_is_ASCII_name(host.name))); + + /* Send the VRFY command (Note: The host name part may be absent when the + host is a local system) */ + result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "VRFY %s%s%s%s", + address, + host.name ? "@" : "", + host.name ? host.name : "", + utf8 ? " SMTPUTF8" : ""); + + Curl_free_idnconverted_hostname(&host); + free(address); + } + 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 = (conn->proto.smtpc.utf8_supported) && + (!strcmp(smtp->custom, "EXPN")); + + /* Send the custom recipient based command such as the EXPN command */ + result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, + "%s %s%s", smtp->custom, + smtp->rcpt->data, + utf8 ? " SMTPUTF8" : ""); + } + } + else + /* Send the non-recipient based command such as HELP */ + result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "%s", + smtp->custom && smtp->custom[0] != '\0' ? + smtp->custom : "HELP"); + + if(!result) + smtp_state(data, SMTP_COMMAND); + + return result; +} + +/*********************************************************************** + * + * smtp_perform_mail() + * + * Sends an MAIL command to initiate the upload of a message. + */ +static CURLcode smtp_perform_mail(struct Curl_easy *data) +{ + char *from = NULL; + char *auth = NULL; + char *size = NULL; + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + /* We notify the server we are sending UTF-8 data if a) it supports the + SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in + either the local address or host name parts. This is regardless of + whether the host name is encoded using IDN ACE */ + bool utf8 = FALSE; + + /* Calculate the FROM parameter */ + if(data->set.str[STRING_MAIL_FROM]) { + char *address = NULL; + struct hostname host = { NULL, NULL, NULL, NULL }; + + /* Parse the FROM mailbox into the local address and host name parts, + converting the host name to an IDN A-label if necessary */ + result = smtp_parse_address(data->set.str[STRING_MAIL_FROM], + &address, &host); + if(result) + return result; + + /* Establish whether we should report SMTPUTF8 to the server for this + mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ + utf8 = (conn->proto.smtpc.utf8_supported) && + ((host.encalloc) || (!Curl_is_ASCII_name(address)) || + (!Curl_is_ASCII_name(host.name))); + + if(host.name) { + from = aprintf("<%s@%s>", address, host.name); + + Curl_free_idnconverted_hostname(&host); + } + else + /* An invalid mailbox was provided but we'll simply let the server worry + about that and reply with a 501 error */ + from = aprintf("<%s>", address); + + free(address); + } + else + /* Null reverse-path, RFC-5321, sect. 3.6.3 */ + from = strdup("<>"); + + if(!from) + return CURLE_OUT_OF_MEMORY; + + /* Calculate the optional AUTH parameter */ + if(data->set.str[STRING_MAIL_AUTH] && conn->proto.smtpc.sasl.authused) { + if(data->set.str[STRING_MAIL_AUTH][0] != '\0') { + char *address = NULL; + struct hostname host = { NULL, NULL, NULL, NULL }; + + /* Parse the AUTH mailbox into the local address and host name parts, + converting the host name to an IDN A-label if necessary */ + result = smtp_parse_address(data->set.str[STRING_MAIL_AUTH], + &address, &host); + if(result) { + free(from); + return result; + } + + /* Establish whether we should report SMTPUTF8 to the server for this + mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ + if((!utf8) && (conn->proto.smtpc.utf8_supported) && + ((host.encalloc) || (!Curl_is_ASCII_name(address)) || + (!Curl_is_ASCII_name(host.name)))) + utf8 = TRUE; + + if(host.name) { + auth = aprintf("<%s@%s>", address, host.name); + + Curl_free_idnconverted_hostname(&host); + } + else + /* An invalid mailbox was provided but we'll simply let the server + worry about it */ + auth = aprintf("<%s>", address); + + free(address); + } + else + /* Empty AUTH, RFC-2554, sect. 5 */ + auth = strdup("<>"); + + if(!auth) { + free(from); + + return CURLE_OUT_OF_MEMORY; + } + } + + /* Prepare the mime data if some. */ + if(data->set.mimepost.kind != MIMEKIND_NONE) { + /* Use the whole structure as data. */ + data->set.mimepost.flags &= ~MIME_BODY_ONLY; + + /* Add external headers and mime version. */ + curl_mime_headers(&data->set.mimepost, data->set.headers, 0); + result = Curl_mime_prepare_headers(data, &data->set.mimepost, NULL, + NULL, MIMESTRATEGY_MAIL); + + if(!result) + if(!Curl_checkheaders(data, STRCONST("Mime-Version"))) + result = Curl_mime_add_header(&data->set.mimepost.curlheaders, + "Mime-Version: 1.0"); + + /* Make sure we will read the entire mime structure. */ + if(!result) + result = Curl_mime_rewind(&data->set.mimepost); + + if(result) { + free(from); + free(auth); + + return result; + } + + data->state.infilesize = Curl_mime_size(&data->set.mimepost); + + /* Read from mime structure. */ + data->state.fread_func = (curl_read_callback) Curl_mime_read; + data->state.in = (void *) &data->set.mimepost; + } + + /* Calculate the optional SIZE parameter */ + if(conn->proto.smtpc.size_supported && data->state.infilesize > 0) { + size = aprintf("%" CURL_FORMAT_CURL_OFF_T, data->state.infilesize); + + if(!size) { + free(from); + free(auth); + + return CURLE_OUT_OF_MEMORY; + } + } + + /* If the mailboxes in the FROM and AUTH parameters don't include a UTF-8 + based address then quickly scan through the recipient list and check if + any there do, as we need to correctly identify our support for SMTPUTF8 + in the envelope, as per RFC-6531 sect. 3.4 */ + if(conn->proto.smtpc.utf8_supported && !utf8) { + struct SMTP *smtp = data->req.p.smtp; + struct curl_slist *rcpt = smtp->rcpt; + + while(rcpt && !utf8) { + /* Does the host name contain non-ASCII characters? */ + if(!Curl_is_ASCII_name(rcpt->data)) + utf8 = TRUE; + + rcpt = rcpt->next; + } + } + + /* Send the MAIL command */ + result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, + "MAIL FROM:%s%s%s%s%s%s", + from, /* Mandatory */ + auth ? " AUTH=" : "", /* Optional on AUTH support */ + auth ? auth : "", /* */ + size ? " SIZE=" : "", /* Optional on SIZE support */ + size ? size : "", /* */ + utf8 ? " SMTPUTF8" /* Internationalised mailbox */ + : ""); /* included in our envelope */ + + free(from); + free(auth); + free(size); + + if(!result) + smtp_state(data, SMTP_MAIL); + + return result; +} + +/*********************************************************************** + * + * smtp_perform_rcpt_to() + * + * Sends a RCPT TO command for a given recipient as part of the message upload + * process. + */ +static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct SMTP *smtp = data->req.p.smtp; + char *address = NULL; + struct hostname host = { NULL, NULL, NULL, NULL }; + + /* Parse the recipient mailbox into the local address and host name parts, + converting the host name to an IDN A-label if necessary */ + result = smtp_parse_address(smtp->rcpt->data, + &address, &host); + if(result) + return result; + + /* Send the RCPT TO command */ + if(host.name) + result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "RCPT TO:<%s@%s>", + address, host.name); + else + /* An invalid mailbox was provided but we'll simply let the server worry + about that and reply with a 501 error */ + result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "RCPT TO:<%s>", + address); + + Curl_free_idnconverted_hostname(&host); + free(address); + + if(!result) + smtp_state(data, SMTP_RCPT); + + return result; +} + +/*********************************************************************** + * + * smtp_perform_quit() + * + * Performs the quit action prior to sclose() being called. + */ +static CURLcode smtp_perform_quit(struct Curl_easy *data, + struct connectdata *conn) +{ + /* Send the QUIT command */ + CURLcode result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "%s", "QUIT"); + + if(!result) + smtp_state(data, SMTP_QUIT); + + return result; +} + +/* For the initial server greeting */ +static CURLcode smtp_state_servergreet_resp(struct Curl_easy *data, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + (void)instate; /* no use for this yet */ + + if(smtpcode/100 != 2) { + failf(data, "Got unexpected smtp-server response: %d", smtpcode); + result = CURLE_WEIRD_SERVER_REPLY; + } + else + result = smtp_perform_ehlo(data); + + return result; +} + +/* For STARTTLS responses */ +static CURLcode smtp_state_starttls_resp(struct Curl_easy *data, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + (void)instate; /* no use for this yet */ + + /* Pipelining in response is forbidden. */ + if(data->conn->proto.smtpc.pp.cache_size) + return CURLE_WEIRD_SERVER_REPLY; + + if(smtpcode != 220) { + if(data->set.use_ssl != CURLUSESSL_TRY) { + failf(data, "STARTTLS denied, code %d", smtpcode); + result = CURLE_USE_SSL_FAILED; + } + else + result = smtp_perform_authentication(data); + } + else + result = smtp_perform_upgrade_tls(data); + + return result; +} + +/* For EHLO responses */ +static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, + struct connectdata *conn, int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct smtp_conn *smtpc = &conn->proto.smtpc; + const char *line = data->state.buffer; + size_t len = strlen(line); + + (void)instate; /* no use for this yet */ + + if(smtpcode/100 != 2 && smtpcode != 1) { + if(data->set.use_ssl <= CURLUSESSL_TRY + || Curl_conn_is_ssl(conn, FIRSTSOCKET)) + result = smtp_perform_helo(data, conn); + else { + failf(data, "Remote access denied: %d", smtpcode); + result = CURLE_REMOTE_ACCESS_DENIED; + } + } + else if(len >= 4) { + line += 4; + len -= 4; + + /* Does the server support the STARTTLS capability? */ + if(len >= 8 && !memcmp(line, "STARTTLS", 8)) + smtpc->tls_supported = TRUE; + + /* Does the server support the SIZE capability? */ + else if(len >= 4 && !memcmp(line, "SIZE", 4)) + smtpc->size_supported = TRUE; + + /* Does the server support the UTF-8 capability? */ + else if(len >= 8 && !memcmp(line, "SMTPUTF8", 8)) + smtpc->utf8_supported = TRUE; + + /* Does the server support authentication? */ + else if(len >= 5 && !memcmp(line, "AUTH ", 5)) { + smtpc->auth_supported = TRUE; + + /* Advance past the AUTH keyword */ + line += 5; + len -= 5; + + /* Loop through the data line */ + for(;;) { + size_t llen; + size_t wordlen; + unsigned short mechbit; + + while(len && + (*line == ' ' || *line == '\t' || + *line == '\r' || *line == '\n')) { + + line++; + len--; + } + + if(!len) + break; + + /* Extract the word */ + for(wordlen = 0; wordlen < len && line[wordlen] != ' ' && + line[wordlen] != '\t' && line[wordlen] != '\r' && + line[wordlen] != '\n';) + wordlen++; + + /* Test the word for a matching authentication mechanism */ + mechbit = Curl_sasl_decode_mech(line, wordlen, &llen); + if(mechbit && llen == wordlen) + smtpc->sasl.authmechs |= mechbit; + + line += wordlen; + len -= wordlen; + } + } + + if(smtpcode != 1) { + if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + /* We don't have a SSL/TLS connection yet, but SSL is requested */ + if(smtpc->tls_supported) + /* Switch to TLS connection now */ + result = smtp_perform_starttls(data, conn); + else if(data->set.use_ssl == CURLUSESSL_TRY) + /* Fallback and carry on with authentication */ + result = smtp_perform_authentication(data); + else { + failf(data, "STARTTLS not supported."); + result = CURLE_USE_SSL_FAILED; + } + } + else + result = smtp_perform_authentication(data); + } + } + else { + failf(data, "Unexpectedly short EHLO response"); + result = CURLE_WEIRD_SERVER_REPLY; + } + + return result; +} + +/* For HELO responses */ +static CURLcode smtp_state_helo_resp(struct Curl_easy *data, int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + (void)instate; /* no use for this yet */ + + if(smtpcode/100 != 2) { + failf(data, "Remote access denied: %d", smtpcode); + result = CURLE_REMOTE_ACCESS_DENIED; + } + else + /* End of connect phase */ + smtp_state(data, SMTP_STOP); + + return result; +} + +/* For SASL authentication responses */ +static CURLcode smtp_state_auth_resp(struct Curl_easy *data, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct smtp_conn *smtpc = &conn->proto.smtpc; + saslprogress progress; + + (void)instate; /* no use for this yet */ + + result = Curl_sasl_continue(&smtpc->sasl, data, smtpcode, &progress); + if(!result) + switch(progress) { + case SASL_DONE: + smtp_state(data, SMTP_STOP); /* Authenticated */ + break; + case SASL_IDLE: /* No mechanism left after cancellation */ + failf(data, "Authentication cancelled"); + result = CURLE_LOGIN_DENIED; + break; + default: + break; + } + + return result; +} + +/* For command responses */ +static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SMTP *smtp = data->req.p.smtp; + char *line = data->state.buffer; + size_t len = strlen(line); + + (void)instate; /* no use for this yet */ + + if((smtp->rcpt && smtpcode/100 != 2 && smtpcode != 553 && smtpcode != 1) || + (!smtp->rcpt && smtpcode/100 != 2 && smtpcode != 1)) { + failf(data, "Command failed: %d", smtpcode); + result = CURLE_WEIRD_SERVER_REPLY; + } + else { + /* Temporarily add the LF character back and send as body to the client */ + if(!data->req.no_body) { + line[len] = '\n'; + result = Curl_client_write(data, CLIENTWRITE_BODY, line, len + 1); + line[len] = '\0'; + } + + if(smtpcode != 1) { + if(smtp->rcpt) { + smtp->rcpt = smtp->rcpt->next; + + if(smtp->rcpt) { + /* Send the next command */ + result = smtp_perform_command(data); + } + else + /* End of DO phase */ + smtp_state(data, SMTP_STOP); + } + else + /* End of DO phase */ + smtp_state(data, SMTP_STOP); + } + } + + return result; +} + +/* For MAIL responses */ +static CURLcode smtp_state_mail_resp(struct Curl_easy *data, int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + (void)instate; /* no use for this yet */ + + if(smtpcode/100 != 2) { + failf(data, "MAIL failed: %d", smtpcode); + result = CURLE_SEND_ERROR; + } + else + /* Start the RCPT TO command */ + result = smtp_perform_rcpt_to(data); + + return result; +} + +/* For RCPT responses */ +static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, + struct connectdata *conn, int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SMTP *smtp = data->req.p.smtp; + bool is_smtp_err = FALSE; + bool is_smtp_blocking_err = FALSE; + + (void)instate; /* no use for this yet */ + + is_smtp_err = (smtpcode/100 != 2) ? TRUE : FALSE; + + /* If there's multiple RCPT TO to be issued, it's possible to ignore errors + and proceed with only the valid addresses. */ + is_smtp_blocking_err = + (is_smtp_err && !data->set.mail_rcpt_allowfails) ? TRUE : FALSE; + + if(is_smtp_err) { + /* Remembering the last failure which we can report if all "RCPT TO" have + failed and we cannot proceed. */ + smtp->rcpt_last_error = smtpcode; + + if(is_smtp_blocking_err) { + failf(data, "RCPT failed: %d", smtpcode); + result = CURLE_SEND_ERROR; + } + } + else { + /* Some RCPT TO commands have succeeded. */ + smtp->rcpt_had_ok = TRUE; + } + + if(!is_smtp_blocking_err) { + smtp->rcpt = smtp->rcpt->next; + + if(smtp->rcpt) + /* Send the next RCPT TO command */ + result = smtp_perform_rcpt_to(data); + else { + /* We weren't able to issue a successful RCPT TO command while going + over recipients (potentially multiple). Sending back last error. */ + if(!smtp->rcpt_had_ok) { + failf(data, "RCPT failed: %d (last error)", smtp->rcpt_last_error); + result = CURLE_SEND_ERROR; + } + else { + /* Send the DATA command */ + result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "%s", "DATA"); + + if(!result) + smtp_state(data, SMTP_DATA); + } + } + } + + return result; +} + +/* For DATA response */ +static CURLcode smtp_state_data_resp(struct Curl_easy *data, int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + (void)instate; /* no use for this yet */ + + if(smtpcode != 354) { + failf(data, "DATA failed: %d", smtpcode); + result = CURLE_SEND_ERROR; + } + else { + /* Set the progress upload size */ + Curl_pgrsSetUploadSize(data, data->state.infilesize); + + /* SMTP upload */ + Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + + /* End of DO phase */ + smtp_state(data, SMTP_STOP); + } + + return result; +} + +/* For POSTDATA responses, which are received after the entire DATA + part has been sent to the server */ +static CURLcode smtp_state_postdata_resp(struct Curl_easy *data, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 250) + result = CURLE_WEIRD_SERVER_REPLY; + + /* End of DONE phase */ + smtp_state(data, SMTP_STOP); + + return result; +} + +static CURLcode smtp_statemachine(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int smtpcode; + struct smtp_conn *smtpc = &conn->proto.smtpc; + struct pingpong *pp = &smtpc->pp; + size_t nread = 0; + + /* Busy upgrading the connection; right now all I/O is SSL/TLS, not SMTP */ + if(smtpc->state == SMTP_UPGRADETLS) + return smtp_perform_upgrade_tls(data); + + /* Flush any data that needs to be sent */ + if(pp->sendleft) + return Curl_pp_flushsend(data, pp); + + do { + /* Read the response from the server */ + result = Curl_pp_readresp(data, sock, pp, &smtpcode, &nread); + if(result) + return result; + + /* Store the latest response for later retrieval if necessary */ + if(smtpc->state != SMTP_QUIT && smtpcode != 1) + data->info.httpcode = smtpcode; + + if(!smtpcode) + break; + + /* We have now received a full SMTP server response */ + switch(smtpc->state) { + case SMTP_SERVERGREET: + result = smtp_state_servergreet_resp(data, smtpcode, smtpc->state); + break; + + case SMTP_EHLO: + result = smtp_state_ehlo_resp(data, conn, smtpcode, smtpc->state); + break; + + case SMTP_HELO: + result = smtp_state_helo_resp(data, smtpcode, smtpc->state); + break; + + case SMTP_STARTTLS: + result = smtp_state_starttls_resp(data, smtpcode, smtpc->state); + break; + + case SMTP_AUTH: + result = smtp_state_auth_resp(data, smtpcode, smtpc->state); + break; + + case SMTP_COMMAND: + result = smtp_state_command_resp(data, smtpcode, smtpc->state); + break; + + case SMTP_MAIL: + result = smtp_state_mail_resp(data, smtpcode, smtpc->state); + break; + + case SMTP_RCPT: + result = smtp_state_rcpt_resp(data, conn, smtpcode, smtpc->state); + break; + + case SMTP_DATA: + result = smtp_state_data_resp(data, smtpcode, smtpc->state); + break; + + case SMTP_POSTDATA: + result = smtp_state_postdata_resp(data, smtpcode, smtpc->state); + break; + + case SMTP_QUIT: + /* fallthrough, just stop! */ + default: + /* internal error */ + smtp_state(data, SMTP_STOP); + break; + } + } while(!result && smtpc->state != SMTP_STOP && Curl_pp_moredata(pp)); + + return result; +} + +/* Called repeatedly until done from multi.c */ +static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct smtp_conn *smtpc = &conn->proto.smtpc; + + if((conn->handler->flags & PROTOPT_SSL) && !smtpc->ssldone) { + bool ssldone = FALSE; + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + smtpc->ssldone = ssldone; + if(result || !smtpc->ssldone) + return result; + } + + result = Curl_pp_statemach(data, &smtpc->pp, FALSE, FALSE); + *done = (smtpc->state == SMTP_STOP) ? TRUE : FALSE; + + return result; +} + +static CURLcode smtp_block_statemach(struct Curl_easy *data, + struct connectdata *conn, + bool disconnecting) +{ + CURLcode result = CURLE_OK; + struct smtp_conn *smtpc = &conn->proto.smtpc; + + while(smtpc->state != SMTP_STOP && !result) + result = Curl_pp_statemach(data, &smtpc->pp, TRUE, disconnecting); + + return result; +} + +/* Allocate and initialize the SMTP struct for the current Curl_easy if + required */ +static CURLcode smtp_init(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct SMTP *smtp; + + smtp = data->req.p.smtp = calloc(1, sizeof(struct SMTP)); + if(!smtp) + result = CURLE_OUT_OF_MEMORY; + + return result; +} + +/* For the SMTP "protocol connect" and "doing" phases only */ +static int smtp_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) +{ + return Curl_pp_getsock(data, &conn->proto.smtpc.pp, socks); +} + +/*********************************************************************** + * + * smtp_connect() + * + * This function should do everything that is to be considered a part of + * the connection phase. + * + * The variable pointed to by 'done' will be TRUE if the protocol-layer + * connect phase is done when this function returns, or FALSE if not. + */ +static CURLcode smtp_connect(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct smtp_conn *smtpc = &conn->proto.smtpc; + struct pingpong *pp = &smtpc->pp; + + *done = FALSE; /* default to not done yet */ + + /* We always support persistent connections in SMTP */ + connkeep(conn, "SMTP default"); + + PINGPONG_SETUP(pp, smtp_statemachine, smtp_endofresp); + + /* Initialize the SASL storage */ + Curl_sasl_init(&smtpc->sasl, data, &saslsmtp); + + /* Initialise the pingpong layer */ + Curl_pp_setup(pp); + Curl_pp_init(data, pp); + + /* Parse the URL options */ + result = smtp_parse_url_options(conn); + if(result) + return result; + + /* Parse the URL path */ + result = smtp_parse_url_path(data); + if(result) + return result; + + /* Start off waiting for the server greeting response */ + smtp_state(data, SMTP_SERVERGREET); + + result = smtp_multi_statemach(data, done); + + return result; +} + +/*********************************************************************** + * + * smtp_done() + * + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. + */ +static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, + bool premature) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct SMTP *smtp = data->req.p.smtp; + struct pingpong *pp = &conn->proto.smtpc.pp; + char *eob; + ssize_t len; + ssize_t bytes_written; + + (void)premature; + + if(!smtp) + return CURLE_OK; + + /* Cleanup our per-request based variables */ + Curl_safefree(smtp->custom); + + if(status) { + connclose(conn, "SMTP done with bad status"); /* marked for closure */ + result = status; /* use the already set error code */ + } + else if(!data->set.connect_only && data->set.mail_rcpt && + (data->state.upload || data->set.mimepost.kind)) { + /* Calculate the EOB taking into account any terminating CRLF from the + previous line of the email or the CRLF of the DATA command when there + is "no mail data". RFC-5321, sect. 4.1.1.4. + + Note: As some SSL backends, such as OpenSSL, will cause Curl_write() to + fail when using a different pointer following a previous write, that + returned CURLE_AGAIN, we duplicate the EOB now rather than when the + bytes written doesn't equal len. */ + if(smtp->trailing_crlf || !data->state.infilesize) { + eob = strdup(&SMTP_EOB[2]); + len = SMTP_EOB_LEN - 2; + } + else { + eob = strdup(SMTP_EOB); + len = SMTP_EOB_LEN; + } + + if(!eob) + return CURLE_OUT_OF_MEMORY; + + /* Send the end of block data */ + result = Curl_write(data, conn->writesockfd, eob, len, &bytes_written); + if(result) { + free(eob); + return result; + } + + if(bytes_written != len) { + /* The whole chunk was not sent so keep it around and adjust the + pingpong structure accordingly */ + pp->sendthis = eob; + pp->sendsize = len; + pp->sendleft = len - bytes_written; + } + else { + /* Successfully sent so adjust the response timeout relative to now */ + pp->response = Curl_now(); + + free(eob); + } + + smtp_state(data, SMTP_POSTDATA); + + /* Run the state-machine */ + result = smtp_block_statemach(data, conn, FALSE); + } + + /* Clear the transfer mode for the next request */ + smtp->transfer = PPTRANSFER_BODY; + + return result; +} + +/*********************************************************************** + * + * smtp_perform() + * + * This is the actual DO function for SMTP. Transfer a mail, send a command + * or get some data according to the options previously setup. + */ +static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, + bool *dophase_done) +{ + /* This is SMTP and no proxy */ + CURLcode result = CURLE_OK; + struct SMTP *smtp = data->req.p.smtp; + + DEBUGF(infof(data, "DO phase starts")); + + if(data->req.no_body) { + /* Requested no body means no transfer */ + smtp->transfer = PPTRANSFER_INFO; + } + + *dophase_done = FALSE; /* not done yet */ + + /* Store the first recipient (or NULL if not specified) */ + smtp->rcpt = data->set.mail_rcpt; + + /* Track of whether we've successfully sent at least one RCPT TO command */ + smtp->rcpt_had_ok = FALSE; + + /* Track of the last error we've received by sending RCPT TO command */ + smtp->rcpt_last_error = 0; + + /* Initial data character is the first character in line: it is implicitly + preceded by a virtual CRLF. */ + smtp->trailing_crlf = TRUE; + smtp->eob = 2; + + /* Start the first command in the DO phase */ + if((data->state.upload || data->set.mimepost.kind) && data->set.mail_rcpt) + /* MAIL transfer */ + result = smtp_perform_mail(data); + else + /* SMTP based command (VRFY, EXPN, NOOP, RSET or HELP) */ + result = smtp_perform_command(data); + + if(result) + return result; + + /* Run the state-machine */ + result = smtp_multi_statemach(data, dophase_done); + + *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); + + if(*dophase_done) + DEBUGF(infof(data, "DO phase is complete")); + + return result; +} + +/*********************************************************************** + * + * smtp_do() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (smtp_perform). + * + * The input argument is already checked for validity. + */ +static CURLcode smtp_do(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + *done = FALSE; /* default to false */ + + /* Parse the custom request */ + result = smtp_parse_custom_request(data); + if(result) + return result; + + result = smtp_regular_transfer(data, done); + + return result; +} + +/*********************************************************************** + * + * smtp_disconnect() + * + * Disconnect from an SMTP server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +static CURLcode smtp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + struct smtp_conn *smtpc = &conn->proto.smtpc; + (void)data; + + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. */ + + if(!dead_connection && conn->bits.protoconnstart) { + if(!smtp_perform_quit(data, conn)) + (void)smtp_block_statemach(data, conn, TRUE); /* ignore errors on QUIT */ + } + + /* Disconnect from the server */ + Curl_pp_disconnect(&smtpc->pp); + + /* Cleanup the SASL module */ + Curl_sasl_cleanup(conn, smtpc->sasl.authused); + + /* Cleanup our connection based variables */ + Curl_safefree(smtpc->domain); + + return CURLE_OK; +} + +/* Call this when the DO phase has completed */ +static CURLcode smtp_dophase_done(struct Curl_easy *data, bool connected) +{ + struct SMTP *smtp = data->req.p.smtp; + + (void)connected; + + if(smtp->transfer != PPTRANSFER_BODY) + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + + return CURLE_OK; +} + +/* Called from multi.c while DOing */ +static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) +{ + CURLcode result = smtp_multi_statemach(data, dophase_done); + + if(result) + DEBUGF(infof(data, "DO phase failed")); + else if(*dophase_done) { + result = smtp_dophase_done(data, FALSE /* not connected */); + + DEBUGF(infof(data, "DO phase is complete")); + } + + return result; +} + +/*********************************************************************** + * + * smtp_regular_transfer() + * + * The input argument is already checked for validity. + * + * Performs all commands done before a regular transfer between a local and a + * remote host. + */ +static CURLcode smtp_regular_transfer(struct Curl_easy *data, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + bool connected = FALSE; + + /* Make sure size is unknown at this point */ + data->req.size = -1; + + /* Set the progress data */ + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, -1); + Curl_pgrsSetDownloadSize(data, -1); + + /* Carry out the perform */ + result = smtp_perform(data, &connected, dophase_done); + + /* Perform post DO phase operations if necessary */ + if(!result && *dophase_done) + result = smtp_dophase_done(data, connected); + + return result; +} + +static CURLcode smtp_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result; + + /* Clear the TLS upgraded flag */ + conn->bits.tls_upgraded = FALSE; + + /* Initialise the SMTP layer */ + result = smtp_init(data); + if(result) + return result; + + return CURLE_OK; +} + +/*********************************************************************** + * + * smtp_parse_url_options() + * + * Parse the URL login options. + */ +static CURLcode smtp_parse_url_options(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct smtp_conn *smtpc = &conn->proto.smtpc; + const char *ptr = conn->options; + + while(!result && ptr && *ptr) { + const char *key = ptr; + const char *value; + + while(*ptr && *ptr != '=') + ptr++; + + value = ptr + 1; + + while(*ptr && *ptr != ';') + ptr++; + + if(strncasecompare(key, "AUTH=", 5)) + result = Curl_sasl_parse_url_auth_option(&smtpc->sasl, + value, ptr - value); + else + result = CURLE_URL_MALFORMAT; + + if(*ptr == ';') + ptr++; + } + + return result; +} + +/*********************************************************************** + * + * smtp_parse_url_path() + * + * Parse the URL path into separate path components. + */ +static CURLcode smtp_parse_url_path(struct Curl_easy *data) +{ + /* The SMTP struct is already initialised in smtp_connect() */ + struct connectdata *conn = data->conn; + struct smtp_conn *smtpc = &conn->proto.smtpc; + const char *path = &data->state.up.path[1]; /* skip leading path */ + char localhost[HOSTNAME_MAX + 1]; + + /* Calculate the path if necessary */ + if(!*path) { + if(!Curl_gethostname(localhost, sizeof(localhost))) + path = localhost; + else + path = "localhost"; + } + + /* URL decode the path and use it as the domain in our EHLO */ + return Curl_urldecode(path, 0, &smtpc->domain, NULL, REJECT_CTRL); +} + +/*********************************************************************** + * + * smtp_parse_custom_request() + * + * Parse the custom request. + */ +static CURLcode smtp_parse_custom_request(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct SMTP *smtp = data->req.p.smtp; + const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + + /* URL decode the custom request */ + if(custom) + result = Curl_urldecode(custom, 0, &smtp->custom, NULL, REJECT_CTRL); + + return result; +} + +/*********************************************************************** + * + * smtp_parse_address() + * + * Parse the fully qualified mailbox address into a local address part and the + * host name, converting the host name to an IDN A-label, as per RFC-5890, if + * necessary. + * + * Parameters: + * + * conn [in] - The connection handle. + * fqma [in] - The fully qualified mailbox address (which may or + * may not contain UTF-8 characters). + * address [in/out] - A new allocated buffer which holds the local + * address part of the mailbox. This buffer must be + * free'ed by the caller. + * host [in/out] - The host name structure that holds the original, + * and optionally encoded, host name. + * Curl_free_idnconverted_hostname() must be called + * once the caller has finished with the structure. + * + * Returns CURLE_OK on success. + * + * Notes: + * + * Should a UTF-8 host name require conversion to IDN ACE and we cannot honor + * that conversion then we shall return success. This allow the caller to send + * the data to the server as a U-label (as per RFC-6531 sect. 3.2). + * + * If an mailbox '@' separator cannot be located then the mailbox is considered + * to be either a local mailbox or an invalid mailbox (depending on what the + * calling function deems it to be) then the input will simply be returned in + * the address part with the host name being NULL. + */ +static CURLcode smtp_parse_address(const char *fqma, char **address, + struct hostname *host) +{ + CURLcode result = CURLE_OK; + size_t length; + + /* Duplicate the fully qualified email address so we can manipulate it, + ensuring it doesn't contain the delimiters if specified */ + char *dup = strdup(fqma[0] == '<' ? fqma + 1 : fqma); + if(!dup) + return CURLE_OUT_OF_MEMORY; + + length = strlen(dup); + if(length) { + if(dup[length - 1] == '>') + dup[length - 1] = '\0'; + } + + /* Extract the host name from the address (if we can) */ + host->name = strpbrk(dup, "@"); + if(host->name) { + *host->name = '\0'; + host->name = host->name + 1; + + /* Attempt to convert the host name to IDN ACE */ + (void) Curl_idnconvert_hostname(host); + + /* If Curl_idnconvert_hostname() fails then we shall attempt to continue + and send the host name using UTF-8 rather than as 7-bit ACE (which is + our preference) */ + } + + /* Extract the local address from the mailbox */ + *address = dup; + + return result; +} + +CURLcode Curl_smtp_escape_eob(struct Curl_easy *data, + const ssize_t nread, + const ssize_t offset) +{ + /* When sending a SMTP payload we must detect CRLF. sequences making sure + they are sent as CRLF.. instead, as a . on the beginning of a line will + be deleted by the server when not part of an EOB terminator and a + genuine CRLF.CRLF which isn't escaped will wrongly be detected as end of + data by the server + */ + ssize_t i; + ssize_t si; + struct SMTP *smtp = data->req.p.smtp; + char *scratch = data->state.scratch; + char *newscratch = NULL; + char *oldscratch = NULL; + size_t eob_sent; + + /* Do we need to allocate a scratch buffer? */ + if(!scratch || data->set.crlf) { + oldscratch = scratch; + + scratch = newscratch = malloc(2 * data->set.upload_buffer_size); + if(!newscratch) { + failf(data, "Failed to alloc scratch buffer"); + + return CURLE_OUT_OF_MEMORY; + } + } + DEBUGASSERT((size_t)data->set.upload_buffer_size >= (size_t)nread); + + /* Have we already sent part of the EOB? */ + eob_sent = smtp->eob; + + /* This loop can be improved by some kind of Boyer-Moore style of + approach but that is saved for later... */ + if(offset) + memcpy(scratch, data->req.upload_fromhere, offset); + for(i = offset, si = offset; i < nread; i++) { + if(SMTP_EOB[smtp->eob] == data->req.upload_fromhere[i]) { + smtp->eob++; + + /* Is the EOB potentially the terminating CRLF? */ + if(2 == smtp->eob || SMTP_EOB_LEN == smtp->eob) + smtp->trailing_crlf = TRUE; + else + smtp->trailing_crlf = FALSE; + } + else if(smtp->eob) { + /* A previous substring matched so output that first */ + memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent); + si += smtp->eob - eob_sent; + + /* Then compare the first byte */ + if(SMTP_EOB[0] == data->req.upload_fromhere[i]) + smtp->eob = 1; + else + smtp->eob = 0; + + eob_sent = 0; + + /* Reset the trailing CRLF flag as there was more data */ + smtp->trailing_crlf = FALSE; + } + + /* Do we have a match for CRLF. as per RFC-5321, sect. 4.5.2 */ + if(SMTP_EOB_FIND_LEN == smtp->eob) { + /* Copy the replacement data to the target buffer */ + memcpy(&scratch[si], &SMTP_EOB_REPL[eob_sent], + SMTP_EOB_REPL_LEN - eob_sent); + si += SMTP_EOB_REPL_LEN - eob_sent; + smtp->eob = 0; + eob_sent = 0; + } + else if(!smtp->eob) + scratch[si++] = data->req.upload_fromhere[i]; + } + + if(smtp->eob - eob_sent) { + /* A substring matched before processing ended so output that now */ + memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent); + si += smtp->eob - eob_sent; + } + + /* Only use the new buffer if we replaced something */ + if(si != nread) { + /* Upload from the new (replaced) buffer instead */ + data->req.upload_fromhere = scratch; + + /* Save the buffer so it can be freed later */ + data->state.scratch = scratch; + + /* Free the old scratch buffer */ + free(oldscratch); + + /* Set the new amount too */ + data->req.upload_present = si; + } + else + free(newscratch); + + return CURLE_OK; +} + +#endif /* CURL_DISABLE_SMTP */ diff --git a/Utilities/cmcurl/lib/smtp.h b/Utilities/cmcurl/lib/smtp.h new file mode 100644 index 0000000..7a04c21 --- /dev/null +++ b/Utilities/cmcurl/lib/smtp.h @@ -0,0 +1,100 @@ +#ifndef HEADER_CURL_SMTP_H +#define HEADER_CURL_SMTP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "pingpong.h" +#include "curl_sasl.h" + +/**************************************************************************** + * SMTP unique setup + ***************************************************************************/ +typedef enum { + SMTP_STOP, /* do nothing state, stops the state machine */ + SMTP_SERVERGREET, /* waiting for the initial greeting immediately after + a connect */ + SMTP_EHLO, + SMTP_HELO, + SMTP_STARTTLS, + SMTP_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS + (multi mode only) */ + SMTP_AUTH, + SMTP_COMMAND, /* VRFY, EXPN, NOOP, RSET and HELP */ + SMTP_MAIL, /* MAIL FROM */ + SMTP_RCPT, /* RCPT TO */ + SMTP_DATA, + SMTP_POSTDATA, + SMTP_QUIT, + SMTP_LAST /* never used */ +} smtpstate; + +/* This SMTP struct is used in the Curl_easy. All SMTP data that is + connection-oriented must be in smtp_conn to properly deal with the fact that + perhaps the Curl_easy is changed between the times the connection is + used. */ +struct SMTP { + curl_pp_transfer transfer; + char *custom; /* Custom Request */ + struct curl_slist *rcpt; /* Recipient list */ + int rcpt_last_error; /* The last error received for RCPT TO command */ + size_t eob; /* Number of bytes of the EOB (End Of Body) that + have been received so far */ + BIT(rcpt_had_ok); /* Whether any of RCPT TO commands (depends on + total number of recipients) succeeded so far */ + BIT(trailing_crlf); /* Specifies if the trailing CRLF is present */ +}; + +/* smtp_conn is used for struct connection-oriented data in the connectdata + struct */ +struct smtp_conn { + struct pingpong pp; + struct SASL sasl; /* SASL-related storage */ + smtpstate state; /* Always use smtp.c:state() to change state! */ + char *domain; /* Client address/name to send in the EHLO */ + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(tls_supported); /* StartTLS capability supported by server */ + BIT(size_supported); /* If server supports SIZE extension according to + RFC 1870 */ + BIT(utf8_supported); /* If server supports SMTPUTF8 extension according + to RFC 6531 */ + BIT(auth_supported); /* AUTH capability supported by server */ +}; + +extern const struct Curl_handler Curl_handler_smtp; +extern const struct Curl_handler Curl_handler_smtps; + +/* this is the 5-bytes End-Of-Body marker for SMTP */ +#define SMTP_EOB "\x0d\x0a\x2e\x0d\x0a" +#define SMTP_EOB_LEN 5 +#define SMTP_EOB_FIND_LEN 3 + +/* if found in data, replace it with this string instead */ +#define SMTP_EOB_REPL "\x0d\x0a\x2e\x2e" +#define SMTP_EOB_REPL_LEN 4 + +CURLcode Curl_smtp_escape_eob(struct Curl_easy *data, + const ssize_t nread, + const ssize_t offset); + +#endif /* HEADER_CURL_SMTP_H */ diff --git a/Utilities/cmcurl/lib/sockaddr.h b/Utilities/cmcurl/lib/sockaddr.h new file mode 100644 index 0000000..5a6bb20 --- /dev/null +++ b/Utilities/cmcurl/lib/sockaddr.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_SOCKADDR_H +#define HEADER_CURL_SOCKADDR_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +struct Curl_sockaddr_storage { + union { + struct sockaddr sa; + struct sockaddr_in sa_in; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 sa_in6; +#endif +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE + struct sockaddr_storage sa_stor; +#else + char cbuf[256]; /* this should be big enough to fit a lot */ +#endif + } buffer; +}; + +#endif /* HEADER_CURL_SOCKADDR_H */ diff --git a/Utilities/cmcurl/lib/socketpair.c b/Utilities/cmcurl/lib/socketpair.c new file mode 100644 index 0000000..e3d40ff --- /dev/null +++ b/Utilities/cmcurl/lib/socketpair.c @@ -0,0 +1,193 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "socketpair.h" +#include "urldata.h" +#include "rand.h" + +#if !defined(HAVE_SOCKETPAIR) && !defined(CURL_DISABLE_SOCKETPAIR) +#ifdef _WIN32 +/* + * This is a socketpair() implementation for Windows. + */ +#include <string.h> +#include <winsock2.h> +#include <ws2tcpip.h> +#include <windows.h> +#include <io.h> +#else +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> /* IPPROTO_TCP */ +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifndef INADDR_LOOPBACK +#define INADDR_LOOPBACK 0x7f000001 +#endif /* !INADDR_LOOPBACK */ +#endif /* !_WIN32 */ + +#include "nonblock.h" /* for curlx_nonblock */ +#include "timeval.h" /* needed before select.h */ +#include "select.h" /* for Curl_poll */ + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +int Curl_socketpair(int domain, int type, int protocol, + curl_socket_t socks[2]) +{ + union { + struct sockaddr_in inaddr; + struct sockaddr addr; + } a; + curl_socket_t listener; + curl_socklen_t addrlen = sizeof(a.inaddr); + int reuse = 1; + struct pollfd pfd[1]; + (void)domain; + (void)type; + (void)protocol; + + listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(listener == CURL_SOCKET_BAD) + return -1; + + memset(&a, 0, sizeof(a)); + a.inaddr.sin_family = AF_INET; + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_port = 0; + + socks[0] = socks[1] = CURL_SOCKET_BAD; + +#if defined(_WIN32) || defined(__CYGWIN__) + /* don't set SO_REUSEADDR on Windows */ + (void)reuse; +#ifdef SO_EXCLUSIVEADDRUSE + { + int exclusive = 1; + if(setsockopt(listener, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + (char *)&exclusive, (curl_socklen_t)sizeof(exclusive)) == -1) + goto error; + } +#endif +#else + if(setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, + (char *)&reuse, (curl_socklen_t)sizeof(reuse)) == -1) + goto error; +#endif + if(bind(listener, &a.addr, sizeof(a.inaddr)) == -1) + goto error; + if(getsockname(listener, &a.addr, &addrlen) == -1 || + addrlen < (int)sizeof(a.inaddr)) + goto error; + if(listen(listener, 1) == -1) + goto error; + socks[0] = socket(AF_INET, SOCK_STREAM, 0); + if(socks[0] == CURL_SOCKET_BAD) + goto error; + if(connect(socks[0], &a.addr, sizeof(a.inaddr)) == -1) + goto error; + + /* use non-blocking accept to make sure we don't block forever */ + if(curlx_nonblock(listener, TRUE) < 0) + goto error; + pfd[0].fd = listener; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + (void)Curl_poll(pfd, 1, 1000); /* one second */ + socks[1] = accept(listener, NULL, NULL); + if(socks[1] == CURL_SOCKET_BAD) + goto error; + else { + struct curltime start = Curl_now(); + char rnd[9]; + char check[sizeof(rnd)]; + char *p = &check[0]; + size_t s = sizeof(check); + + if(Curl_rand(NULL, (unsigned char *)rnd, sizeof(rnd))) + goto error; + + /* write data to the socket */ + swrite(socks[0], rnd, sizeof(rnd)); + /* verify that we read the correct data */ + do { + ssize_t nread; + + pfd[0].fd = socks[1]; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + (void)Curl_poll(pfd, 1, 1000); /* one second */ + + nread = sread(socks[1], p, s); + if(nread == -1) { + int sockerr = SOCKERRNO; + /* Don't block forever */ + if(Curl_timediff(Curl_now(), start) > (60 * 1000)) + goto error; + if( +#ifdef WSAEWOULDBLOCK + /* This is how Windows does it */ + (WSAEWOULDBLOCK == sockerr) +#else + /* errno may be EWOULDBLOCK or on some systems EAGAIN when it + returned due to its inability to send off data without + blocking. We therefore treat both error codes the same here */ + (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || + (EINTR == sockerr) || (EINPROGRESS == sockerr) +#endif + ) { + continue; + } + goto error; + } + s -= nread; + if(s) { + p += nread; + continue; + } + if(memcmp(rnd, check, sizeof(check))) + goto error; + break; + } while(1); + } + + sclose(listener); + return 0; + +error: + sclose(listener); + sclose(socks[0]); + sclose(socks[1]); + return -1; +} + +#endif /* ! HAVE_SOCKETPAIR */ diff --git a/Utilities/cmcurl/lib/socketpair.h b/Utilities/cmcurl/lib/socketpair.h new file mode 100644 index 0000000..bd499ab --- /dev/null +++ b/Utilities/cmcurl/lib/socketpair.h @@ -0,0 +1,54 @@ +#ifndef HEADER_CURL_SOCKETPAIR_H +#define HEADER_CURL_SOCKETPAIR_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_PIPE + +#define wakeup_write write +#define wakeup_read read +#define wakeup_close close +#define wakeup_create pipe + +#else /* HAVE_PIPE */ + +#define wakeup_write swrite +#define wakeup_read sread +#define wakeup_close sclose +#define wakeup_create(p) Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, p) + +#endif /* HAVE_PIPE */ + +#ifndef HAVE_SOCKETPAIR +#include <curl/curl.h> + +int Curl_socketpair(int domain, int type, int protocol, + curl_socket_t socks[2]); +#else +#define Curl_socketpair(a,b,c,d) socketpair(a,b,c,d) +#endif + +#endif /* HEADER_CURL_SOCKETPAIR_H */ diff --git a/Utilities/cmcurl/lib/socks.c b/Utilities/cmcurl/lib/socks.c new file mode 100644 index 0000000..3a396de --- /dev/null +++ b/Utilities/cmcurl/lib/socks.c @@ -0,0 +1,1263 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_PROXY) + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#include "urldata.h" +#include "sendf.h" +#include "select.h" +#include "cfilters.h" +#include "connect.h" +#include "timeval.h" +#include "socks.h" +#include "multiif.h" /* for getsock macros */ +#include "inet_pton.h" +#include "url.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* for the (SOCKS) connect state machine */ +enum connect_t { + CONNECT_INIT, + CONNECT_SOCKS_INIT, /* 1 */ + CONNECT_SOCKS_SEND, /* 2 waiting to send more first data */ + CONNECT_SOCKS_READ_INIT, /* 3 set up read */ + CONNECT_SOCKS_READ, /* 4 read server response */ + CONNECT_GSSAPI_INIT, /* 5 */ + CONNECT_AUTH_INIT, /* 6 setup outgoing auth buffer */ + CONNECT_AUTH_SEND, /* 7 send auth */ + CONNECT_AUTH_READ, /* 8 read auth response */ + CONNECT_REQ_INIT, /* 9 init SOCKS "request" */ + CONNECT_RESOLVING, /* 10 */ + CONNECT_RESOLVED, /* 11 */ + CONNECT_RESOLVE_REMOTE, /* 12 */ + CONNECT_REQ_SEND, /* 13 */ + CONNECT_REQ_SENDING, /* 14 */ + CONNECT_REQ_READ, /* 15 */ + CONNECT_REQ_READ_MORE, /* 16 */ + CONNECT_DONE /* 17 connected fine to the remote or the SOCKS proxy */ +}; + +struct socks_state { + enum connect_t state; + ssize_t outstanding; /* send this many bytes more */ + unsigned char *outp; /* send from this pointer */ + + const char *hostname; + int remote_port; + const char *proxy_user; + const char *proxy_password; +}; + +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) +/* + * Helper read-from-socket functions. Does the same as Curl_read() but it + * blocks until all bytes amount of buffersize will be read. No more, no less. + * + * This is STUPID BLOCKING behavior. Only used by the SOCKS GSSAPI functions. + */ +int Curl_blockread_all(struct Curl_cfilter *cf, + struct Curl_easy *data, /* transfer */ + char *buf, /* store read data here */ + ssize_t buffersize, /* max amount to read */ + ssize_t *n) /* amount bytes read */ +{ + ssize_t nread = 0; + ssize_t allread = 0; + int result; + CURLcode err = CURLE_OK; + + *n = 0; + for(;;) { + timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + if(timeout_ms < 0) { + /* we already got the timeout */ + result = CURLE_OPERATION_TIMEDOUT; + break; + } + if(!timeout_ms) + timeout_ms = TIMEDIFF_T_MAX; + if(SOCKET_READABLE(cf->conn->sock[cf->sockindex], timeout_ms) <= 0) { + result = ~CURLE_OK; + break; + } + nread = Curl_conn_cf_recv(cf->next, data, buf, buffersize, &err); + if(nread <= 0) { + result = err; + if(CURLE_AGAIN == err) + continue; + if(err) { + break; + } + } + + if(buffersize == nread) { + allread += nread; + *n = allread; + result = CURLE_OK; + break; + } + if(!nread) { + result = ~CURLE_OK; + break; + } + + buffersize -= nread; + buf += nread; + allread += nread; + } + return result; +} +#endif + +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) +#define DEBUG_AND_VERBOSE +#define sxstate(x,d,y) socksstate(x,d,y, __LINE__) +#else +#define sxstate(x,d,y) socksstate(x,d,y) +#endif + +/* always use this function to change state, to make debugging easier */ +static void socksstate(struct socks_state *sx, struct Curl_easy *data, + enum connect_t state +#ifdef DEBUG_AND_VERBOSE + , int lineno +#endif +) +{ + enum connect_t oldstate = sx->state; +#ifdef DEBUG_AND_VERBOSE + /* synced with the state list in urldata.h */ + static const char * const socks_statename[] = { + "INIT", + "SOCKS_INIT", + "SOCKS_SEND", + "SOCKS_READ_INIT", + "SOCKS_READ", + "GSSAPI_INIT", + "AUTH_INIT", + "AUTH_SEND", + "AUTH_READ", + "REQ_INIT", + "RESOLVING", + "RESOLVED", + "RESOLVE_REMOTE", + "REQ_SEND", + "REQ_SENDING", + "REQ_READ", + "REQ_READ_MORE", + "DONE" + }; +#endif + + (void)data; + if(oldstate == state) + /* don't bother when the new state is the same as the old state */ + return; + + sx->state = state; + +#ifdef DEBUG_AND_VERBOSE + infof(data, + "SXSTATE: %s => %s; line %d", + socks_statename[oldstate], socks_statename[sx->state], + lineno); +#endif +} + +static CURLproxycode socks_state_send(struct Curl_cfilter *cf, + struct socks_state *sx, + struct Curl_easy *data, + CURLproxycode failcode, + const char *description) +{ + ssize_t nwritten; + CURLcode result; + + nwritten = Curl_conn_cf_send(cf->next, data, (char *)sx->outp, + sx->outstanding, &result); + if(nwritten <= 0) { + if(CURLE_AGAIN == result) { + return CURLPX_OK; + } + else if(CURLE_OK == result) { + /* connection closed */ + failf(data, "connection to proxy closed"); + return CURLPX_CLOSED; + } + failf(data, "Failed to send %s: %s", description, + curl_easy_strerror(result)); + return failcode; + } + DEBUGASSERT(sx->outstanding >= nwritten); + /* not done, remain in state */ + sx->outstanding -= nwritten; + sx->outp += nwritten; + return CURLPX_OK; +} + +static CURLproxycode socks_state_recv(struct Curl_cfilter *cf, + struct socks_state *sx, + struct Curl_easy *data, + CURLproxycode failcode, + const char *description) +{ + ssize_t nread; + CURLcode result; + + nread = Curl_conn_cf_recv(cf->next, data, (char *)sx->outp, + sx->outstanding, &result); + if(nread <= 0) { + if(CURLE_AGAIN == result) { + return CURLPX_OK; + } + else if(CURLE_OK == result) { + /* connection closed */ + failf(data, "connection to proxy closed"); + return CURLPX_CLOSED; + } + failf(data, "SOCKS4: Failed receiving %s: %s", description, + curl_easy_strerror(result)); + return failcode; + } + /* remain in reading state */ + DEBUGASSERT(sx->outstanding >= nread); + sx->outstanding -= nread; + sx->outp += nread; + return CURLPX_OK; +} + +/* +* This function logs in to a SOCKS4 proxy and sends the specifics to the final +* destination server. +* +* Reference : +* https://www.openssh.com/txt/socks4.protocol +* +* Note : +* Set protocol4a=true for "SOCKS 4A (Simple Extension to SOCKS 4 Protocol)" +* Nonsupport "Identification Protocol (RFC1413)" +*/ +static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, + struct socks_state *sx, + struct Curl_easy *data) +{ + struct connectdata *conn = cf->conn; + const bool protocol4a = + (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE; + unsigned char *socksreq = (unsigned char *)data->state.buffer; + CURLcode result; + CURLproxycode presult; + struct Curl_dns_entry *dns = NULL; + + /* make sure that the buffer is at least 600 bytes */ + DEBUGASSERT(READBUFFER_MIN >= 600); + + switch(sx->state) { + case CONNECT_SOCKS_INIT: + /* SOCKS4 can only do IPv4, insist! */ + conn->ip_version = CURL_IPRESOLVE_V4; + if(conn->bits.httpproxy) + infof(data, "SOCKS4%s: connecting to HTTP proxy %s port %d", + protocol4a ? "a" : "", sx->hostname, sx->remote_port); + + infof(data, "SOCKS4 communication to %s:%d", + sx->hostname, sx->remote_port); + + /* + * Compose socks4 request + * + * Request format + * + * +----+----+----+----+----+----+----+----+----+----+....+----+ + * | VN | CD | DSTPORT | DSTIP | USERID |NULL| + * +----+----+----+----+----+----+----+----+----+----+....+----+ + * # of bytes: 1 1 2 4 variable 1 + */ + + socksreq[0] = 4; /* version (SOCKS4) */ + socksreq[1] = 1; /* connect */ + socksreq[2] = (unsigned char)((sx->remote_port >> 8) & 0xff); /* MSB */ + socksreq[3] = (unsigned char)(sx->remote_port & 0xff); /* LSB */ + + /* DNS resolve only for SOCKS4, not SOCKS4a */ + if(!protocol4a) { + enum resolve_t rc = + Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns); + + if(rc == CURLRESOLV_ERROR) + return CURLPX_RESOLVE_HOST; + else if(rc == CURLRESOLV_PENDING) { + sxstate(sx, data, CONNECT_RESOLVING); + infof(data, "SOCKS4 non-blocking resolve of %s", sx->hostname); + return CURLPX_OK; + } + sxstate(sx, data, CONNECT_RESOLVED); + goto CONNECT_RESOLVED; + } + + /* socks4a doesn't resolve anything locally */ + sxstate(sx, data, CONNECT_REQ_INIT); + goto CONNECT_REQ_INIT; + + case CONNECT_RESOLVING: + /* check if we have the name resolved by now */ + dns = Curl_fetch_addr(data, sx->hostname, (int)conn->port); + + if(dns) { +#ifdef CURLRES_ASYNCH + conn->resolve_async.dns = dns; + conn->resolve_async.done = TRUE; +#endif + infof(data, "Hostname '%s' was found", sx->hostname); + sxstate(sx, data, CONNECT_RESOLVED); + } + else { + result = Curl_resolv_check(data, &dns); + if(!dns) { + if(result) + return CURLPX_RESOLVE_HOST; + return CURLPX_OK; + } + } + /* FALLTHROUGH */ +CONNECT_RESOLVED: + case CONNECT_RESOLVED: { + struct Curl_addrinfo *hp = NULL; + /* + * We cannot use 'hostent' as a struct that Curl_resolv() returns. It + * returns a Curl_addrinfo pointer that may not always look the same. + */ + if(dns) { + hp = dns->addr; + + /* scan for the first IPv4 address */ + while(hp && (hp->ai_family != AF_INET)) + hp = hp->ai_next; + + if(hp) { + struct sockaddr_in *saddr_in; + char buf[64]; + Curl_printable_address(hp, buf, sizeof(buf)); + + saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr; + socksreq[4] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[0]; + socksreq[5] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[1]; + socksreq[6] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[2]; + socksreq[7] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[3]; + + infof(data, "SOCKS4 connect to IPv4 %s (locally resolved)", buf); + + Curl_resolv_unlock(data, dns); /* not used anymore from now on */ + } + else + failf(data, "SOCKS4 connection to %s not supported", sx->hostname); + } + else + failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", + sx->hostname); + + if(!hp) + return CURLPX_RESOLVE_HOST; + } + /* FALLTHROUGH */ +CONNECT_REQ_INIT: + case CONNECT_REQ_INIT: + /* + * This is currently not supporting "Identification Protocol (RFC1413)". + */ + socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ + if(sx->proxy_user) { + size_t plen = strlen(sx->proxy_user); + if(plen > 255) { + /* there is no real size limit to this field in the protocol, but + SOCKS5 limits the proxy user field to 255 bytes and it seems likely + that a longer field is either a mistake or malicious input */ + failf(data, "Too long SOCKS proxy user name"); + return CURLPX_LONG_USER; + } + /* copy the proxy name WITH trailing zero */ + memcpy(socksreq + 8, sx->proxy_user, plen + 1); + } + + /* + * Make connection + */ + { + size_t packetsize = 9 + + strlen((char *)socksreq + 8); /* size including NUL */ + + /* If SOCKS4a, set special invalid IP address 0.0.0.x */ + if(protocol4a) { + size_t hostnamelen = 0; + socksreq[4] = 0; + socksreq[5] = 0; + socksreq[6] = 0; + socksreq[7] = 1; + /* append hostname */ + hostnamelen = strlen(sx->hostname) + 1; /* length including NUL */ + if((hostnamelen <= 255) && + (packetsize + hostnamelen < data->set.buffer_size)) + strcpy((char *)socksreq + packetsize, sx->hostname); + else { + failf(data, "SOCKS4: too long host name"); + return CURLPX_LONG_HOSTNAME; + } + packetsize += hostnamelen; + } + sx->outp = socksreq; + sx->outstanding = packetsize; + sxstate(sx, data, CONNECT_REQ_SENDING); + } + /* FALLTHROUGH */ + case CONNECT_REQ_SENDING: + /* Send request */ + presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, + "SOCKS4 connect request"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in sending state */ + return CURLPX_OK; + } + /* done sending! */ + sx->outstanding = 8; /* receive data size */ + sx->outp = socksreq; + sxstate(sx, data, CONNECT_SOCKS_READ); + + /* FALLTHROUGH */ + case CONNECT_SOCKS_READ: + /* Receive response */ + presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, + "connect request ack"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in reading state */ + return CURLPX_OK; + } + sxstate(sx, data, CONNECT_DONE); + break; + default: /* lots of unused states in SOCKS4 */ + break; + } + + /* + * Response format + * + * +----+----+----+----+----+----+----+----+ + * | VN | CD | DSTPORT | DSTIP | + * +----+----+----+----+----+----+----+----+ + * # of bytes: 1 1 2 4 + * + * VN is the version of the reply code and should be 0. CD is the result + * code with one of the following values: + * + * 90: request granted + * 91: request rejected or failed + * 92: request rejected because SOCKS server cannot connect to + * identd on the client + * 93: request rejected because the client program and identd + * report different user-ids + */ + + /* wrong version ? */ + if(socksreq[0]) { + failf(data, + "SOCKS4 reply has wrong version, version should be 0."); + return CURLPX_BAD_VERSION; + } + + /* Result */ + switch(socksreq[1]) { + case 90: + infof(data, "SOCKS4%s request granted.", protocol4a?"a":""); + break; + case 91: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", request rejected or failed.", + socksreq[4], socksreq[5], socksreq[6], socksreq[7], + (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), + (unsigned char)socksreq[1]); + return CURLPX_REQUEST_FAILED; + case 92: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", request rejected because SOCKS server cannot connect to " + "identd on the client.", + socksreq[4], socksreq[5], socksreq[6], socksreq[7], + (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), + (unsigned char)socksreq[1]); + return CURLPX_IDENTD; + case 93: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", request rejected because the client program and identd " + "report different user-ids.", + socksreq[4], socksreq[5], socksreq[6], socksreq[7], + (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), + (unsigned char)socksreq[1]); + return CURLPX_IDENTD_DIFFER; + default: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", Unknown.", + socksreq[4], socksreq[5], socksreq[6], socksreq[7], + (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), + (unsigned char)socksreq[1]); + return CURLPX_UNKNOWN_FAIL; + } + + return CURLPX_OK; /* Proxy was successful! */ +} + +/* + * This function logs in to a SOCKS5 proxy and sends the specifics to the final + * destination server. + */ +static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, + struct socks_state *sx, + struct Curl_easy *data) +{ + /* + According to the RFC1928, section "6. Replies". This is what a SOCK5 + replies: + + +----+-----+-------+------+----------+----------+ + |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + +----+-----+-------+------+----------+----------+ + | 1 | 1 | X'00' | 1 | Variable | 2 | + +----+-----+-------+------+----------+----------+ + + Where: + + o VER protocol version: X'05' + o REP Reply field: + o X'00' succeeded + */ + struct connectdata *conn = cf->conn; + unsigned char *socksreq = (unsigned char *)data->state.buffer; + int idx; + CURLcode result; + CURLproxycode presult; + bool socks5_resolve_local = + (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE; + const size_t hostname_len = strlen(sx->hostname); + ssize_t len = 0; + const unsigned char auth = data->set.socks5auth; + bool allow_gssapi = FALSE; + struct Curl_dns_entry *dns = NULL; + + DEBUGASSERT(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI)); + switch(sx->state) { + case CONNECT_SOCKS_INIT: + if(conn->bits.httpproxy) + infof(data, "SOCKS5: connecting to HTTP proxy %s port %d", + sx->hostname, sx->remote_port); + + /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ + if(!socks5_resolve_local && hostname_len > 255) { + failf(data, "SOCKS5: the destination hostname is too long to be " + "resolved remotely by the proxy."); + return CURLPX_LONG_HOSTNAME; + } + + if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) + infof(data, + "warning: unsupported value passed to CURLOPT_SOCKS5_AUTH: %u", + auth); + if(!(auth & CURLAUTH_BASIC)) + /* disable username/password auth */ + sx->proxy_user = NULL; +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + if(auth & CURLAUTH_GSSAPI) + allow_gssapi = TRUE; +#endif + + idx = 0; + socksreq[idx++] = 5; /* version */ + idx++; /* number of authentication methods */ + socksreq[idx++] = 0; /* no authentication */ + if(allow_gssapi) + socksreq[idx++] = 1; /* GSS-API */ + if(sx->proxy_user) + socksreq[idx++] = 2; /* username/password */ + /* write the number of authentication methods */ + socksreq[1] = (unsigned char) (idx - 2); + + sx->outp = socksreq; + sx->outstanding = idx; + presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, + "initial SOCKS5 request"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in sending state */ + return CURLPX_OK; + } + sxstate(sx, data, CONNECT_SOCKS_READ); + goto CONNECT_SOCKS_READ_INIT; + case CONNECT_SOCKS_SEND: + presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, + "initial SOCKS5 request"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in sending state */ + return CURLPX_OK; + } + /* FALLTHROUGH */ +CONNECT_SOCKS_READ_INIT: + case CONNECT_SOCKS_READ_INIT: + sx->outstanding = 2; /* expect two bytes */ + sx->outp = socksreq; /* store it here */ + /* FALLTHROUGH */ + case CONNECT_SOCKS_READ: + presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, + "initial SOCKS5 response"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in reading state */ + return CURLPX_OK; + } + else if(socksreq[0] != 5) { + failf(data, "Received invalid version in initial SOCKS5 response."); + return CURLPX_BAD_VERSION; + } + else if(socksreq[1] == 0) { + /* DONE! No authentication needed. Send request. */ + sxstate(sx, data, CONNECT_REQ_INIT); + goto CONNECT_REQ_INIT; + } + else if(socksreq[1] == 2) { + /* regular name + password authentication */ + sxstate(sx, data, CONNECT_AUTH_INIT); + goto CONNECT_AUTH_INIT; + } +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + else if(allow_gssapi && (socksreq[1] == 1)) { + sxstate(sx, data, CONNECT_GSSAPI_INIT); + result = Curl_SOCKS5_gssapi_negotiate(cf, data); + if(result) { + failf(data, "Unable to negotiate SOCKS5 GSS-API context."); + return CURLPX_GSSAPI; + } + } +#endif + else { + /* error */ + if(!allow_gssapi && (socksreq[1] == 1)) { + failf(data, + "SOCKS5 GSSAPI per-message authentication is not supported."); + return CURLPX_GSSAPI_PERMSG; + } + else if(socksreq[1] == 255) { + failf(data, "No authentication method was acceptable."); + return CURLPX_NO_AUTH; + } + } + failf(data, + "Undocumented SOCKS5 mode attempted to be used by server."); + return CURLPX_UNKNOWN_MODE; +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + case CONNECT_GSSAPI_INIT: + /* GSSAPI stuff done non-blocking */ + break; +#endif + + default: /* do nothing! */ + break; + +CONNECT_AUTH_INIT: + case CONNECT_AUTH_INIT: { + /* Needs user name and password */ + size_t proxy_user_len, proxy_password_len; + if(sx->proxy_user && sx->proxy_password) { + proxy_user_len = strlen(sx->proxy_user); + proxy_password_len = strlen(sx->proxy_password); + } + else { + proxy_user_len = 0; + proxy_password_len = 0; + } + + /* username/password request looks like + * +----+------+----------+------+----------+ + * |VER | ULEN | UNAME | PLEN | PASSWD | + * +----+------+----------+------+----------+ + * | 1 | 1 | 1 to 255 | 1 | 1 to 255 | + * +----+------+----------+------+----------+ + */ + len = 0; + socksreq[len++] = 1; /* username/pw subnegotiation version */ + socksreq[len++] = (unsigned char) proxy_user_len; + if(sx->proxy_user && proxy_user_len) { + /* the length must fit in a single byte */ + if(proxy_user_len > 255) { + failf(data, "Excessive user name length for proxy auth"); + return CURLPX_LONG_USER; + } + memcpy(socksreq + len, sx->proxy_user, proxy_user_len); + } + len += proxy_user_len; + socksreq[len++] = (unsigned char) proxy_password_len; + if(sx->proxy_password && proxy_password_len) { + /* the length must fit in a single byte */ + if(proxy_password_len > 255) { + failf(data, "Excessive password length for proxy auth"); + return CURLPX_LONG_PASSWD; + } + memcpy(socksreq + len, sx->proxy_password, proxy_password_len); + } + len += proxy_password_len; + sxstate(sx, data, CONNECT_AUTH_SEND); + sx->outstanding = len; + sx->outp = socksreq; + } + /* FALLTHROUGH */ + case CONNECT_AUTH_SEND: + presult = socks_state_send(cf, sx, data, CURLPX_SEND_AUTH, + "SOCKS5 sub-negotiation request"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in sending state */ + return CURLPX_OK; + } + sx->outp = socksreq; + sx->outstanding = 2; + sxstate(sx, data, CONNECT_AUTH_READ); + /* FALLTHROUGH */ + case CONNECT_AUTH_READ: + presult = socks_state_recv(cf, sx, data, CURLPX_RECV_AUTH, + "SOCKS5 sub-negotiation response"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in reading state */ + return CURLPX_OK; + } + /* ignore the first (VER) byte */ + else if(socksreq[1]) { /* status */ + failf(data, "User was rejected by the SOCKS5 server (%d %d).", + socksreq[0], socksreq[1]); + return CURLPX_USER_REJECTED; + } + + /* Everything is good so far, user was authenticated! */ + sxstate(sx, data, CONNECT_REQ_INIT); + /* FALLTHROUGH */ +CONNECT_REQ_INIT: + case CONNECT_REQ_INIT: + if(socks5_resolve_local) { + enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port, + TRUE, &dns); + + if(rc == CURLRESOLV_ERROR) + return CURLPX_RESOLVE_HOST; + + if(rc == CURLRESOLV_PENDING) { + sxstate(sx, data, CONNECT_RESOLVING); + return CURLPX_OK; + } + sxstate(sx, data, CONNECT_RESOLVED); + goto CONNECT_RESOLVED; + } + goto CONNECT_RESOLVE_REMOTE; + + case CONNECT_RESOLVING: + /* check if we have the name resolved by now */ + dns = Curl_fetch_addr(data, sx->hostname, sx->remote_port); + + if(dns) { +#ifdef CURLRES_ASYNCH + conn->resolve_async.dns = dns; + conn->resolve_async.done = TRUE; +#endif + infof(data, "SOCKS5: hostname '%s' found", sx->hostname); + } + + if(!dns) { + result = Curl_resolv_check(data, &dns); + if(!dns) { + if(result) + return CURLPX_RESOLVE_HOST; + return CURLPX_OK; + } + } + /* FALLTHROUGH */ +CONNECT_RESOLVED: + case CONNECT_RESOLVED: { + char dest[MAX_IPADR_LEN]; /* printable address */ + struct Curl_addrinfo *hp = NULL; + if(dns) + hp = dns->addr; +#ifdef ENABLE_IPV6 + if(data->set.ipver != CURL_IPRESOLVE_WHATEVER) { + int wanted_family = data->set.ipver == CURL_IPRESOLVE_V4 ? + AF_INET : AF_INET6; + /* scan for the first proper address */ + while(hp && (hp->ai_family != wanted_family)) + hp = hp->ai_next; + } +#endif + if(!hp) { + failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", + sx->hostname); + return CURLPX_RESOLVE_HOST; + } + + Curl_printable_address(hp, dest, sizeof(dest)); + + len = 0; + socksreq[len++] = 5; /* version (SOCKS5) */ + socksreq[len++] = 1; /* connect */ + socksreq[len++] = 0; /* must be zero */ + if(hp->ai_family == AF_INET) { + int i; + struct sockaddr_in *saddr_in; + socksreq[len++] = 1; /* ATYP: IPv4 = 1 */ + + saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr; + for(i = 0; i < 4; i++) { + socksreq[len++] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[i]; + } + + infof(data, "SOCKS5 connect to %s:%d (locally resolved)", dest, + sx->remote_port); + } +#ifdef ENABLE_IPV6 + else if(hp->ai_family == AF_INET6) { + int i; + struct sockaddr_in6 *saddr_in6; + socksreq[len++] = 4; /* ATYP: IPv6 = 4 */ + + saddr_in6 = (struct sockaddr_in6 *)(void *)hp->ai_addr; + for(i = 0; i < 16; i++) { + socksreq[len++] = + ((unsigned char *)&saddr_in6->sin6_addr.s6_addr)[i]; + } + + infof(data, "SOCKS5 connect to [%s]:%d (locally resolved)", dest, + sx->remote_port); + } +#endif + else { + hp = NULL; /* fail! */ + failf(data, "SOCKS5 connection to %s not supported", dest); + } + + Curl_resolv_unlock(data, dns); /* not used anymore from now on */ + goto CONNECT_REQ_SEND; + } +CONNECT_RESOLVE_REMOTE: + case CONNECT_RESOLVE_REMOTE: + /* Authentication is complete, now specify destination to the proxy */ + len = 0; + socksreq[len++] = 5; /* version (SOCKS5) */ + socksreq[len++] = 1; /* connect */ + socksreq[len++] = 0; /* must be zero */ + + if(!socks5_resolve_local) { + /* ATYP: domain name = 3, + IPv6 == 4, + IPv4 == 1 */ + unsigned char ip4[4]; +#ifdef ENABLE_IPV6 + if(conn->bits.ipv6_ip) { + char ip6[16]; + if(1 != Curl_inet_pton(AF_INET6, sx->hostname, ip6)) + return CURLPX_BAD_ADDRESS_TYPE; + socksreq[len++] = 4; + memcpy(&socksreq[len], ip6, sizeof(ip6)); + len += sizeof(ip6); + } + else +#endif + if(1 == Curl_inet_pton(AF_INET, sx->hostname, ip4)) { + socksreq[len++] = 1; + memcpy(&socksreq[len], ip4, sizeof(ip4)); + len += sizeof(ip4); + } + else { + socksreq[len++] = 3; + socksreq[len++] = (unsigned char) hostname_len; /* one byte length */ + memcpy(&socksreq[len], sx->hostname, hostname_len); /* w/o NULL */ + len += hostname_len; + } + infof(data, "SOCKS5 connect to %s:%d (remotely resolved)", + sx->hostname, sx->remote_port); + } + /* FALLTHROUGH */ + +CONNECT_REQ_SEND: + case CONNECT_REQ_SEND: + /* PORT MSB */ + socksreq[len++] = (unsigned char)((sx->remote_port >> 8) & 0xff); + /* PORT LSB */ + socksreq[len++] = (unsigned char)(sx->remote_port & 0xff); + +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + if(conn->socks5_gssapi_enctype) { + failf(data, "SOCKS5 GSS-API protection not yet implemented."); + return CURLPX_GSSAPI_PROTECTION; + } +#endif + sx->outp = socksreq; + sx->outstanding = len; + sxstate(sx, data, CONNECT_REQ_SENDING); + /* FALLTHROUGH */ + case CONNECT_REQ_SENDING: + presult = socks_state_send(cf, sx, data, CURLPX_SEND_REQUEST, + "SOCKS5 connect request"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in send state */ + return CURLPX_OK; + } +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + if(conn->socks5_gssapi_enctype) { + failf(data, "SOCKS5 GSS-API protection not yet implemented."); + return CURLPX_GSSAPI_PROTECTION; + } +#endif + sx->outstanding = 10; /* minimum packet size is 10 */ + sx->outp = socksreq; + sxstate(sx, data, CONNECT_REQ_READ); + /* FALLTHROUGH */ + case CONNECT_REQ_READ: + presult = socks_state_recv(cf, sx, data, CURLPX_RECV_REQACK, + "SOCKS5 connect request ack"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in reading state */ + return CURLPX_OK; + } + else if(socksreq[0] != 5) { /* version */ + failf(data, + "SOCKS5 reply has wrong version, version should be 5."); + return CURLPX_BAD_VERSION; + } + else if(socksreq[1]) { /* Anything besides 0 is an error */ + CURLproxycode rc = CURLPX_REPLY_UNASSIGNED; + int code = socksreq[1]; + failf(data, "Can't complete SOCKS5 connection to %s. (%d)", + sx->hostname, (unsigned char)socksreq[1]); + if(code < 9) { + /* RFC 1928 section 6 lists: */ + static const CURLproxycode lookup[] = { + CURLPX_OK, + CURLPX_REPLY_GENERAL_SERVER_FAILURE, + CURLPX_REPLY_NOT_ALLOWED, + CURLPX_REPLY_NETWORK_UNREACHABLE, + CURLPX_REPLY_HOST_UNREACHABLE, + CURLPX_REPLY_CONNECTION_REFUSED, + CURLPX_REPLY_TTL_EXPIRED, + CURLPX_REPLY_COMMAND_NOT_SUPPORTED, + CURLPX_REPLY_ADDRESS_TYPE_NOT_SUPPORTED, + }; + rc = lookup[code]; + } + return rc; + } + + /* Fix: in general, returned BND.ADDR is variable length parameter by RFC + 1928, so the reply packet should be read until the end to avoid errors + at subsequent protocol level. + + +----+-----+-------+------+----------+----------+ + |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + +----+-----+-------+------+----------+----------+ + | 1 | 1 | X'00' | 1 | Variable | 2 | + +----+-----+-------+------+----------+----------+ + + ATYP: + o IP v4 address: X'01', BND.ADDR = 4 byte + o domain name: X'03', BND.ADDR = [ 1 byte length, string ] + o IP v6 address: X'04', BND.ADDR = 16 byte + */ + + /* Calculate real packet size */ + if(socksreq[3] == 3) { + /* domain name */ + int addrlen = (int) socksreq[4]; + len = 5 + addrlen + 2; + } + else if(socksreq[3] == 4) { + /* IPv6 */ + len = 4 + 16 + 2; + } + else if(socksreq[3] == 1) { + len = 4 + 4 + 2; + } + else { + failf(data, "SOCKS5 reply has wrong address type."); + return CURLPX_BAD_ADDRESS_TYPE; + } + + /* At this point we already read first 10 bytes */ +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + if(!conn->socks5_gssapi_enctype) { + /* decrypt_gssapi_blockread already read the whole packet */ +#endif + if(len > 10) { + sx->outstanding = len - 10; /* get the rest */ + sx->outp = &socksreq[10]; + sxstate(sx, data, CONNECT_REQ_READ_MORE); + } + else { + sxstate(sx, data, CONNECT_DONE); + break; + } +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + } +#endif + /* FALLTHROUGH */ + case CONNECT_REQ_READ_MORE: + presult = socks_state_recv(cf, sx, data, CURLPX_RECV_ADDRESS, + "SOCKS5 connect request address"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in reading state */ + return CURLPX_OK; + } + sxstate(sx, data, CONNECT_DONE); + } + infof(data, "SOCKS5 request granted."); + + return CURLPX_OK; /* Proxy was successful! */ +} + +static CURLcode connect_SOCKS(struct Curl_cfilter *cf, + struct socks_state *sxstate, + struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + CURLproxycode pxresult = CURLPX_OK; + struct connectdata *conn = cf->conn; + + switch(conn->socks_proxy.proxytype) { + case CURLPROXY_SOCKS5: + case CURLPROXY_SOCKS5_HOSTNAME: + pxresult = do_SOCKS5(cf, sxstate, data); + break; + + case CURLPROXY_SOCKS4: + case CURLPROXY_SOCKS4A: + pxresult = do_SOCKS4(cf, sxstate, data); + break; + + default: + failf(data, "unknown proxytype option given"); + result = CURLE_COULDNT_CONNECT; + } /* switch proxytype */ + if(pxresult) { + result = CURLE_PROXY; + data->info.pxcode = pxresult; + } + + return result; +} + +static void socks_proxy_cf_free(struct Curl_cfilter *cf) +{ + struct socks_state *sxstate = cf->ctx; + if(sxstate) { + free(sxstate); + cf->ctx = NULL; + } +} + +/* After a TCP connection to the proxy has been verified, this function does + the next magic steps. If 'done' isn't set TRUE, it is not done yet and + must be called again. + + Note: this function's sub-functions call failf() + +*/ +static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + CURLcode result; + struct connectdata *conn = cf->conn; + int sockindex = cf->sockindex; + struct socks_state *sx = cf->ctx; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + result = cf->next->cft->do_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + + if(!sx) { + sx = calloc(1, sizeof(*sx)); + if(!sx) + return CURLE_OUT_OF_MEMORY; + cf->ctx = sx; + } + + if(sx->state == CONNECT_INIT) { + /* for the secondary socket (FTP), use the "connect to host" + * but ignore the "connect to port" (use the secondary port) + */ + sxstate(sx, data, CONNECT_SOCKS_INIT); + sx->hostname = + conn->bits.httpproxy ? + conn->http_proxy.host.name : + conn->bits.conn_to_host ? + conn->conn_to_host.name : + sockindex == SECONDARYSOCKET ? + conn->secondaryhostname : conn->host.name; + sx->remote_port = + conn->bits.httpproxy ? (int)conn->http_proxy.port : + sockindex == SECONDARYSOCKET ? conn->secondary_port : + conn->bits.conn_to_port ? conn->conn_to_port : + conn->remote_port; + sx->proxy_user = conn->socks_proxy.user; + sx->proxy_password = conn->socks_proxy.passwd; + } + + result = connect_SOCKS(cf, sx, data); + if(!result && sx->state == CONNECT_DONE) { + cf->connected = TRUE; + Curl_verboseconnect(data, conn); + socks_proxy_cf_free(cf); + } + + *done = cf->connected; + return result; +} + +static void socks_cf_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct socks_state *sx = cf->ctx; + + if(!cf->connected && sx) { + /* If we are not connected, the filter below is and has nothing + * to wait on, we determine what to wait for. */ + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); + switch(sx->state) { + case CONNECT_RESOLVING: + case CONNECT_SOCKS_READ: + case CONNECT_AUTH_READ: + case CONNECT_REQ_READ: + case CONNECT_REQ_READ_MORE: + Curl_pollset_set_in_only(data, ps, sock); + break; + default: + Curl_pollset_set_out_only(data, ps, sock); + break; + } + } +} + +static void socks_proxy_cf_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + + DEBUGASSERT(cf->next); + cf->connected = FALSE; + socks_proxy_cf_free(cf); + cf->next->cft->do_close(cf->next, data); +} + +static void socks_proxy_cf_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)data; + socks_proxy_cf_free(cf); +} + +static void socks_cf_get_host(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char **phost, + const char **pdisplay_host, + int *pport) +{ + (void)data; + if(!cf->connected) { + *phost = cf->conn->socks_proxy.host.name; + *pdisplay_host = cf->conn->http_proxy.host.dispname; + *pport = (int)cf->conn->socks_proxy.port; + } + else { + cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport); + } +} + +struct Curl_cftype Curl_cft_socks_proxy = { + "SOCKS-PROXYY", + CF_TYPE_IP_CONNECT, + 0, + socks_proxy_cf_destroy, + socks_proxy_cf_connect, + socks_proxy_cf_close, + socks_cf_get_host, + socks_cf_adjust_pollset, + Curl_cf_def_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + CURLcode result; + + (void)data; + result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, NULL); + if(!result) + Curl_conn_cf_insert_after(cf_at, cf); + return result; +} + +#endif /* CURL_DISABLE_PROXY */ diff --git a/Utilities/cmcurl/lib/socks.h b/Utilities/cmcurl/lib/socks.h new file mode 100644 index 0000000..a3adcc6 --- /dev/null +++ b/Utilities/cmcurl/lib/socks.h @@ -0,0 +1,61 @@ +#ifndef HEADER_CURL_SOCKS_H +#define HEADER_CURL_SOCKS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef CURL_DISABLE_PROXY +#define Curl_SOCKS4(a,b,c,d,e) CURLE_NOT_BUILT_IN +#define Curl_SOCKS5(a,b,c,d,e,f) CURLE_NOT_BUILT_IN +#define Curl_SOCKS_getsock(x,y,z) 0 +#else +/* + * Helper read-from-socket functions. Does the same as Curl_read() but it + * blocks until all bytes amount of buffersize will be read. No more, no less. + * + * This is STUPID BLOCKING behavior + */ +int Curl_blockread_all(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, + ssize_t buffersize, + ssize_t *n); + +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) +/* + * This function handles the SOCKS5 GSS-API negotiation and initialization + */ +CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, + struct Curl_easy *data); +#endif + +CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data); + +extern struct Curl_cftype Curl_cft_socks_proxy; + +#endif /* CURL_DISABLE_PROXY */ + +#endif /* HEADER_CURL_SOCKS_H */ diff --git a/Utilities/cmcurl/lib/socks_gssapi.c b/Utilities/cmcurl/lib/socks_gssapi.c new file mode 100644 index 0000000..2ede8c7 --- /dev/null +++ b/Utilities/cmcurl/lib/socks_gssapi.c @@ -0,0 +1,536 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Markus Moeller, <markus_moeller@compuserve.com> + * + * 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(HAVE_GSSAPI) && !defined(CURL_DISABLE_PROXY) + +#include "curl_gssapi.h" +#include "urldata.h" +#include "sendf.h" +#include "cfilters.h" +#include "connect.h" +#include "timeval.h" +#include "socks.h" +#include "warnless.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +static gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; + +/* + * Helper GSS-API error functions. + */ +static int check_gss_err(struct Curl_easy *data, + OM_uint32 major_status, + OM_uint32 minor_status, + const char *function) +{ + if(GSS_ERROR(major_status)) { + OM_uint32 maj_stat, min_stat; + OM_uint32 msg_ctx = 0; + gss_buffer_desc status_string = GSS_C_EMPTY_BUFFER; + char buf[1024]; + size_t len; + + len = 0; + msg_ctx = 0; + while(!msg_ctx) { + /* convert major status code (GSS-API error) to text */ + maj_stat = gss_display_status(&min_stat, major_status, + GSS_C_GSS_CODE, + GSS_C_NULL_OID, + &msg_ctx, &status_string); + if(maj_stat == GSS_S_COMPLETE) { + if(sizeof(buf) > len + status_string.length + 1) { + strcpy(buf + len, (char *) status_string.value); + len += status_string.length; + } + gss_release_buffer(&min_stat, &status_string); + break; + } + gss_release_buffer(&min_stat, &status_string); + } + if(sizeof(buf) > len + 3) { + strcpy(buf + len, ".\n"); + len += 2; + } + msg_ctx = 0; + while(!msg_ctx) { + /* convert minor status code (underlying routine error) to text */ + maj_stat = gss_display_status(&min_stat, minor_status, + GSS_C_MECH_CODE, + GSS_C_NULL_OID, + &msg_ctx, &status_string); + if(maj_stat == GSS_S_COMPLETE) { + if(sizeof(buf) > len + status_string.length) + strcpy(buf + len, (char *) status_string.value); + gss_release_buffer(&min_stat, &status_string); + break; + } + gss_release_buffer(&min_stat, &status_string); + } + failf(data, "GSS-API error: %s failed: %s", function, buf); + return 1; + } + + return 0; +} + +CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct connectdata *conn = cf->conn; + curl_socket_t sock = conn->sock[cf->sockindex]; + CURLcode code; + ssize_t actualread; + ssize_t nwritten; + int result; + OM_uint32 gss_major_status, gss_minor_status, gss_status; + OM_uint32 gss_ret_flags; + int gss_conf_state, gss_enc; + gss_buffer_desc service = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gss_send_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gss_recv_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gss_w_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc *gss_token = GSS_C_NO_BUFFER; + gss_name_t server = GSS_C_NO_NAME; + gss_name_t gss_client_name = GSS_C_NO_NAME; + unsigned short us_length; + char *user = NULL; + unsigned char socksreq[4]; /* room for GSS-API exchange header only */ + const char *serviceptr = data->set.str[STRING_PROXY_SERVICE_NAME] ? + data->set.str[STRING_PROXY_SERVICE_NAME] : "rcmd"; + const size_t serviceptr_length = strlen(serviceptr); + + /* GSS-API request looks like + * +----+------+-----+----------------+ + * |VER | MTYP | LEN | TOKEN | + * +----+------+----------------------+ + * | 1 | 1 | 2 | up to 2^16 - 1 | + * +----+------+-----+----------------+ + */ + + /* prepare service name */ + if(strchr(serviceptr, '/')) { + service.length = serviceptr_length; + service.value = malloc(service.length); + if(!service.value) + return CURLE_OUT_OF_MEMORY; + memcpy(service.value, serviceptr, service.length); + + gss_major_status = gss_import_name(&gss_minor_status, &service, + (gss_OID) GSS_C_NULL_OID, &server); + } + else { + service.value = malloc(serviceptr_length + + strlen(conn->socks_proxy.host.name) + 2); + if(!service.value) + return CURLE_OUT_OF_MEMORY; + service.length = serviceptr_length + + strlen(conn->socks_proxy.host.name) + 1; + msnprintf(service.value, service.length + 1, "%s@%s", + serviceptr, conn->socks_proxy.host.name); + + gss_major_status = gss_import_name(&gss_minor_status, &service, + GSS_C_NT_HOSTBASED_SERVICE, &server); + } + + gss_release_buffer(&gss_status, &service); /* clear allocated memory */ + + if(check_gss_err(data, gss_major_status, + gss_minor_status, "gss_import_name()")) { + failf(data, "Failed to create service name."); + gss_release_name(&gss_status, &server); + return CURLE_COULDNT_CONNECT; + } + + (void)curlx_nonblock(sock, FALSE); + + /* As long as we need to keep sending some context info, and there's no */ + /* errors, keep sending it... */ + for(;;) { + gss_major_status = Curl_gss_init_sec_context(data, + &gss_minor_status, + &gss_context, + server, + &Curl_krb5_mech_oid, + NULL, + gss_token, + &gss_send_token, + TRUE, + &gss_ret_flags); + + if(gss_token != GSS_C_NO_BUFFER) + gss_release_buffer(&gss_status, &gss_recv_token); + if(check_gss_err(data, gss_major_status, + gss_minor_status, "gss_init_sec_context")) { + gss_release_name(&gss_status, &server); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_release_buffer(&gss_status, &gss_send_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + failf(data, "Failed to initial GSS-API token."); + return CURLE_COULDNT_CONNECT; + } + + if(gss_send_token.length) { + socksreq[0] = 1; /* GSS-API subnegotiation version */ + socksreq[1] = 1; /* authentication message type */ + us_length = htons((short)gss_send_token.length); + memcpy(socksreq + 2, &us_length, sizeof(short)); + + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + if(code || (4 != nwritten)) { + failf(data, "Failed to send GSS-API authentication request."); + gss_release_name(&gss_status, &server); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_release_buffer(&gss_status, &gss_send_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + nwritten = Curl_conn_cf_send(cf->next, data, + (char *)gss_send_token.value, + gss_send_token.length, &code); + if(code || ((ssize_t)gss_send_token.length != nwritten)) { + failf(data, "Failed to send GSS-API authentication token."); + gss_release_name(&gss_status, &server); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_release_buffer(&gss_status, &gss_send_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + } + + gss_release_buffer(&gss_status, &gss_send_token); + gss_release_buffer(&gss_status, &gss_recv_token); + if(gss_major_status != GSS_S_CONTINUE_NEEDED) + break; + + /* analyse response */ + + /* GSS-API response looks like + * +----+------+-----+----------------+ + * |VER | MTYP | LEN | TOKEN | + * +----+------+----------------------+ + * | 1 | 1 | 2 | up to 2^16 - 1 | + * +----+------+-----+----------------+ + */ + + result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); + if(result || (actualread != 4)) { + failf(data, "Failed to receive GSS-API authentication response."); + gss_release_name(&gss_status, &server); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + /* ignore the first (VER) byte */ + if(socksreq[1] == 255) { /* status / message type */ + failf(data, "User was rejected by the SOCKS5 server (%d %d).", + socksreq[0], socksreq[1]); + gss_release_name(&gss_status, &server); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + if(socksreq[1] != 1) { /* status / message type */ + failf(data, "Invalid GSS-API authentication response type (%d %d).", + socksreq[0], socksreq[1]); + gss_release_name(&gss_status, &server); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + memcpy(&us_length, socksreq + 2, sizeof(short)); + us_length = ntohs(us_length); + + gss_recv_token.length = us_length; + gss_recv_token.value = malloc(us_length); + if(!gss_recv_token.value) { + failf(data, + "Could not allocate memory for GSS-API authentication " + "response token."); + gss_release_name(&gss_status, &server); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_OUT_OF_MEMORY; + } + + result = Curl_blockread_all(cf, data, (char *)gss_recv_token.value, + gss_recv_token.length, &actualread); + + if(result || (actualread != us_length)) { + failf(data, "Failed to receive GSS-API authentication token."); + gss_release_name(&gss_status, &server); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + gss_token = &gss_recv_token; + } + + gss_release_name(&gss_status, &server); + + /* Everything is good so far, user was authenticated! */ + gss_major_status = gss_inquire_context(&gss_minor_status, gss_context, + &gss_client_name, NULL, NULL, NULL, + NULL, NULL, NULL); + if(check_gss_err(data, gss_major_status, + gss_minor_status, "gss_inquire_context")) { + gss_delete_sec_context(&gss_status, &gss_context, NULL); + gss_release_name(&gss_status, &gss_client_name); + failf(data, "Failed to determine user name."); + return CURLE_COULDNT_CONNECT; + } + gss_major_status = gss_display_name(&gss_minor_status, gss_client_name, + &gss_send_token, NULL); + if(check_gss_err(data, gss_major_status, + gss_minor_status, "gss_display_name")) { + gss_delete_sec_context(&gss_status, &gss_context, NULL); + gss_release_name(&gss_status, &gss_client_name); + gss_release_buffer(&gss_status, &gss_send_token); + failf(data, "Failed to determine user name."); + return CURLE_COULDNT_CONNECT; + } + user = malloc(gss_send_token.length + 1); + if(!user) { + gss_delete_sec_context(&gss_status, &gss_context, NULL); + gss_release_name(&gss_status, &gss_client_name); + gss_release_buffer(&gss_status, &gss_send_token); + return CURLE_OUT_OF_MEMORY; + } + + memcpy(user, gss_send_token.value, gss_send_token.length); + user[gss_send_token.length] = '\0'; + gss_release_name(&gss_status, &gss_client_name); + gss_release_buffer(&gss_status, &gss_send_token); + infof(data, "SOCKS5 server authenticated user %s with GSS-API.",user); + free(user); + user = NULL; + + /* Do encryption */ + socksreq[0] = 1; /* GSS-API subnegotiation version */ + socksreq[1] = 2; /* encryption message type */ + + gss_enc = 0; /* no data protection */ + /* do confidentiality protection if supported */ + if(gss_ret_flags & GSS_C_CONF_FLAG) + gss_enc = 2; + /* else do integrity protection */ + else if(gss_ret_flags & GSS_C_INTEG_FLAG) + gss_enc = 1; + + infof(data, "SOCKS5 server supports GSS-API %s data protection.", + (gss_enc == 0)?"no":((gss_enc==1)?"integrity":"confidentiality")); + /* force for the moment to no data protection */ + gss_enc = 0; + /* + * Sending the encryption type in clear seems wrong. It should be + * protected with gss_seal()/gss_wrap(). See RFC1961 extract below + * The NEC reference implementations on which this is based is + * therefore at fault + * + * +------+------+------+.......................+ + * + ver | mtyp | len | token | + * +------+------+------+.......................+ + * + 0x01 | 0x02 | 0x02 | up to 2^16 - 1 octets | + * +------+------+------+.......................+ + * + * Where: + * + * - "ver" is the protocol version number, here 1 to represent the + * first version of the SOCKS/GSS-API protocol + * + * - "mtyp" is the message type, here 2 to represent a protection + * -level negotiation message + * + * - "len" is the length of the "token" field in octets + * + * - "token" is the GSS-API encapsulated protection level + * + * The token is produced by encapsulating an octet containing the + * required protection level using gss_seal()/gss_wrap() with conf_req + * set to FALSE. The token is verified using gss_unseal()/ + * gss_unwrap(). + * + */ + if(data->set.socks5_gssapi_nec) { + us_length = htons((short)1); + memcpy(socksreq + 2, &us_length, sizeof(short)); + } + else { + gss_send_token.length = 1; + gss_send_token.value = malloc(1); + if(!gss_send_token.value) { + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_OUT_OF_MEMORY; + } + memcpy(gss_send_token.value, &gss_enc, 1); + + gss_major_status = gss_wrap(&gss_minor_status, gss_context, 0, + GSS_C_QOP_DEFAULT, &gss_send_token, + &gss_conf_state, &gss_w_token); + + if(check_gss_err(data, gss_major_status, gss_minor_status, "gss_wrap")) { + gss_release_buffer(&gss_status, &gss_send_token); + gss_release_buffer(&gss_status, &gss_w_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + failf(data, "Failed to wrap GSS-API encryption value into token."); + return CURLE_COULDNT_CONNECT; + } + gss_release_buffer(&gss_status, &gss_send_token); + + us_length = htons((short)gss_w_token.length); + memcpy(socksreq + 2, &us_length, sizeof(short)); + } + + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + if(code || (4 != nwritten)) { + failf(data, "Failed to send GSS-API encryption request."); + gss_release_buffer(&gss_status, &gss_w_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + if(data->set.socks5_gssapi_nec) { + memcpy(socksreq, &gss_enc, 1); + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code); + if(code || ( 1 != nwritten)) { + failf(data, "Failed to send GSS-API encryption type."); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + } + else { + nwritten = Curl_conn_cf_send(cf->next, data, + (char *)gss_w_token.value, + gss_w_token.length, &code); + if(code || ((ssize_t)gss_w_token.length != nwritten)) { + failf(data, "Failed to send GSS-API encryption type."); + gss_release_buffer(&gss_status, &gss_w_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + gss_release_buffer(&gss_status, &gss_w_token); + } + + result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); + if(result || (actualread != 4)) { + failf(data, "Failed to receive GSS-API encryption response."); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + /* ignore the first (VER) byte */ + if(socksreq[1] == 255) { /* status / message type */ + failf(data, "User was rejected by the SOCKS5 server (%d %d).", + socksreq[0], socksreq[1]); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + if(socksreq[1] != 2) { /* status / message type */ + failf(data, "Invalid GSS-API encryption response type (%d %d).", + socksreq[0], socksreq[1]); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + memcpy(&us_length, socksreq + 2, sizeof(short)); + us_length = ntohs(us_length); + + gss_recv_token.length = us_length; + gss_recv_token.value = malloc(gss_recv_token.length); + if(!gss_recv_token.value) { + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_OUT_OF_MEMORY; + } + result = Curl_blockread_all(cf, data, (char *)gss_recv_token.value, + gss_recv_token.length, &actualread); + + if(result || (actualread != us_length)) { + failf(data, "Failed to receive GSS-API encryptrion type."); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + if(!data->set.socks5_gssapi_nec) { + gss_major_status = gss_unwrap(&gss_minor_status, gss_context, + &gss_recv_token, &gss_w_token, + 0, GSS_C_QOP_DEFAULT); + + if(check_gss_err(data, gss_major_status, gss_minor_status, "gss_unwrap")) { + gss_release_buffer(&gss_status, &gss_recv_token); + gss_release_buffer(&gss_status, &gss_w_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + failf(data, "Failed to unwrap GSS-API encryption value into token."); + return CURLE_COULDNT_CONNECT; + } + gss_release_buffer(&gss_status, &gss_recv_token); + + if(gss_w_token.length != 1) { + failf(data, "Invalid GSS-API encryption response length (%zu).", + gss_w_token.length); + gss_release_buffer(&gss_status, &gss_w_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + memcpy(socksreq, gss_w_token.value, gss_w_token.length); + gss_release_buffer(&gss_status, &gss_w_token); + } + else { + if(gss_recv_token.length != 1) { + failf(data, "Invalid GSS-API encryption response length (%zu).", + gss_recv_token.length); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + memcpy(socksreq, gss_recv_token.value, gss_recv_token.length); + gss_release_buffer(&gss_status, &gss_recv_token); + } + + (void)curlx_nonblock(sock, TRUE); + + infof(data, "SOCKS5 access with%s protection granted.", + (socksreq[0] == 0)?"out GSS-API data": + ((socksreq[0] == 1)?" GSS-API integrity":" GSS-API confidentiality")); + + conn->socks5_gssapi_enctype = socksreq[0]; + if(socksreq[0] == 0) + gss_delete_sec_context(&gss_status, &gss_context, NULL); + + return CURLE_OK; +} + +#endif /* HAVE_GSSAPI && !CURL_DISABLE_PROXY */ diff --git a/Utilities/cmcurl/lib/socks_sspi.c b/Utilities/cmcurl/lib/socks_sspi.c new file mode 100644 index 0000000..d1200ea --- /dev/null +++ b/Utilities/cmcurl/lib/socks_sspi.c @@ -0,0 +1,614 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Markus Moeller, <markus_moeller@compuserve.com> + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_WINDOWS_SSPI) && !defined(CURL_DISABLE_PROXY) + +#include "urldata.h" +#include "sendf.h" +#include "cfilters.h" +#include "connect.h" +#include "strerror.h" +#include "timeval.h" +#include "socks.h" +#include "curl_sspi.h" +#include "curl_multibyte.h" +#include "warnless.h" +#include "strdup.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Helper sspi error functions. + */ +static int check_sspi_err(struct Curl_easy *data, + SECURITY_STATUS status, + const char *function) +{ + if(status != SEC_E_OK && + status != SEC_I_COMPLETE_AND_CONTINUE && + status != SEC_I_COMPLETE_NEEDED && + status != SEC_I_CONTINUE_NEEDED) { + char buffer[STRERROR_LEN]; + failf(data, "SSPI error: %s failed: %s", function, + Curl_sspi_strerror(status, buffer, sizeof(buffer))); + return 1; + } + return 0; +} + +/* This is the SSPI-using version of this function */ +CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct connectdata *conn = cf->conn; + curl_socket_t sock = conn->sock[cf->sockindex]; + CURLcode code; + ssize_t actualread; + ssize_t written; + int result; + /* Needs GSS-API authentication */ + SECURITY_STATUS status; + unsigned long sspi_ret_flags = 0; + unsigned char gss_enc; + SecBuffer sspi_send_token, sspi_recv_token, sspi_w_token[3]; + SecBufferDesc input_desc, output_desc, wrap_desc; + SecPkgContext_Sizes sspi_sizes; + CredHandle cred_handle; + CtxtHandle sspi_context; + PCtxtHandle context_handle = NULL; + SecPkgCredentials_Names names; + TimeStamp expiry; + char *service_name = NULL; + unsigned short us_length; + unsigned long qop; + unsigned char socksreq[4]; /* room for GSS-API exchange header only */ + const char *service = data->set.str[STRING_PROXY_SERVICE_NAME] ? + data->set.str[STRING_PROXY_SERVICE_NAME] : "rcmd"; + const size_t service_length = strlen(service); + + /* GSS-API request looks like + * +----+------+-----+----------------+ + * |VER | MTYP | LEN | TOKEN | + * +----+------+----------------------+ + * | 1 | 1 | 2 | up to 2^16 - 1 | + * +----+------+-----+----------------+ + */ + + /* prepare service name */ + if(strchr(service, '/')) { + service_name = strdup(service); + if(!service_name) + return CURLE_OUT_OF_MEMORY; + } + else { + service_name = malloc(service_length + + strlen(conn->socks_proxy.host.name) + 2); + if(!service_name) + return CURLE_OUT_OF_MEMORY; + msnprintf(service_name, service_length + + strlen(conn->socks_proxy.host.name) + 2, "%s/%s", + service, conn->socks_proxy.host.name); + } + + input_desc.cBuffers = 1; + input_desc.pBuffers = &sspi_recv_token; + input_desc.ulVersion = SECBUFFER_VERSION; + + sspi_recv_token.BufferType = SECBUFFER_TOKEN; + sspi_recv_token.cbBuffer = 0; + sspi_recv_token.pvBuffer = NULL; + + output_desc.cBuffers = 1; + output_desc.pBuffers = &sspi_send_token; + output_desc.ulVersion = SECBUFFER_VERSION; + + sspi_send_token.BufferType = SECBUFFER_TOKEN; + sspi_send_token.cbBuffer = 0; + sspi_send_token.pvBuffer = NULL; + + wrap_desc.cBuffers = 3; + wrap_desc.pBuffers = sspi_w_token; + wrap_desc.ulVersion = SECBUFFER_VERSION; + + cred_handle.dwLower = 0; + cred_handle.dwUpper = 0; + + status = s_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *) TEXT("Kerberos"), + SECPKG_CRED_OUTBOUND, + NULL, + NULL, + NULL, + NULL, + &cred_handle, + &expiry); + + if(check_sspi_err(data, status, "AcquireCredentialsHandle")) { + failf(data, "Failed to acquire credentials."); + free(service_name); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + return CURLE_COULDNT_CONNECT; + } + + (void)curlx_nonblock(sock, FALSE); + + /* As long as we need to keep sending some context info, and there's no */ + /* errors, keep sending it... */ + for(;;) { + TCHAR *sname; + + sname = curlx_convert_UTF8_to_tchar(service_name); + if(!sname) + return CURLE_OUT_OF_MEMORY; + + status = s_pSecFn->InitializeSecurityContext(&cred_handle, + context_handle, + sname, + ISC_REQ_MUTUAL_AUTH | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_REPLAY_DETECT, + 0, + SECURITY_NATIVE_DREP, + &input_desc, + 0, + &sspi_context, + &output_desc, + &sspi_ret_flags, + &expiry); + + curlx_unicodefree(sname); + + if(sspi_recv_token.pvBuffer) { + s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + sspi_recv_token.pvBuffer = NULL; + sspi_recv_token.cbBuffer = 0; + } + + if(check_sspi_err(data, status, "InitializeSecurityContext")) { + free(service_name); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + if(sspi_recv_token.pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + failf(data, "Failed to initialise security context."); + return CURLE_COULDNT_CONNECT; + } + + if(sspi_send_token.cbBuffer) { + socksreq[0] = 1; /* GSS-API subnegotiation version */ + socksreq[1] = 1; /* authentication message type */ + us_length = htons((short)sspi_send_token.cbBuffer); + memcpy(socksreq + 2, &us_length, sizeof(short)); + + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + if(code || (4 != written)) { + failf(data, "Failed to send SSPI authentication request."); + free(service_name); + if(sspi_send_token.pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + if(sspi_recv_token.pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + written = Curl_conn_cf_send(cf->next, data, + (char *)sspi_send_token.pvBuffer, + sspi_send_token.cbBuffer, &code); + if(code || (sspi_send_token.cbBuffer != (size_t)written)) { + failf(data, "Failed to send SSPI authentication token."); + free(service_name); + if(sspi_send_token.pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + if(sspi_recv_token.pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + } + + if(sspi_send_token.pvBuffer) { + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + sspi_send_token.pvBuffer = NULL; + } + sspi_send_token.cbBuffer = 0; + + if(sspi_recv_token.pvBuffer) { + s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + sspi_recv_token.pvBuffer = NULL; + } + sspi_recv_token.cbBuffer = 0; + + if(status != SEC_I_CONTINUE_NEEDED) + break; + + /* analyse response */ + + /* GSS-API response looks like + * +----+------+-----+----------------+ + * |VER | MTYP | LEN | TOKEN | + * +----+------+----------------------+ + * | 1 | 1 | 2 | up to 2^16 - 1 | + * +----+------+-----+----------------+ + */ + + result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); + if(result || (actualread != 4)) { + failf(data, "Failed to receive SSPI authentication response."); + free(service_name); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + /* ignore the first (VER) byte */ + if(socksreq[1] == 255) { /* status / message type */ + failf(data, "User was rejected by the SOCKS5 server (%u %u).", + (unsigned int)socksreq[0], (unsigned int)socksreq[1]); + free(service_name); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + if(socksreq[1] != 1) { /* status / message type */ + failf(data, "Invalid SSPI authentication response type (%u %u).", + (unsigned int)socksreq[0], (unsigned int)socksreq[1]); + free(service_name); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + memcpy(&us_length, socksreq + 2, sizeof(short)); + us_length = ntohs(us_length); + + sspi_recv_token.cbBuffer = us_length; + sspi_recv_token.pvBuffer = malloc(us_length); + + if(!sspi_recv_token.pvBuffer) { + free(service_name); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + result = Curl_blockread_all(cf, data, (char *)sspi_recv_token.pvBuffer, + sspi_recv_token.cbBuffer, &actualread); + + if(result || (actualread != us_length)) { + failf(data, "Failed to receive SSPI authentication token."); + free(service_name); + if(sspi_recv_token.pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + context_handle = &sspi_context; + } + + free(service_name); + + /* Everything is good so far, user was authenticated! */ + status = s_pSecFn->QueryCredentialsAttributes(&cred_handle, + SECPKG_CRED_ATTR_NAMES, + &names); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + if(check_sspi_err(data, status, "QueryCredentialAttributes")) { + s_pSecFn->DeleteSecurityContext(&sspi_context); + s_pSecFn->FreeContextBuffer(names.sUserName); + failf(data, "Failed to determine user name."); + return CURLE_COULDNT_CONNECT; + } + infof(data, "SOCKS5 server authenticated user %s with GSS-API.", + names.sUserName); + s_pSecFn->FreeContextBuffer(names.sUserName); + + /* Do encryption */ + socksreq[0] = 1; /* GSS-API subnegotiation version */ + socksreq[1] = 2; /* encryption message type */ + + gss_enc = 0; /* no data protection */ + /* do confidentiality protection if supported */ + if(sspi_ret_flags & ISC_REQ_CONFIDENTIALITY) + gss_enc = 2; + /* else do integrity protection */ + else if(sspi_ret_flags & ISC_REQ_INTEGRITY) + gss_enc = 1; + + infof(data, "SOCKS5 server supports GSS-API %s data protection.", + (gss_enc == 0)?"no":((gss_enc == 1)?"integrity":"confidentiality") ); + /* force to no data protection, avoid encryption/decryption for now */ + gss_enc = 0; + /* + * Sending the encryption type in clear seems wrong. It should be + * protected with gss_seal()/gss_wrap(). See RFC1961 extract below + * The NEC reference implementations on which this is based is + * therefore at fault + * + * +------+------+------+.......................+ + * + ver | mtyp | len | token | + * +------+------+------+.......................+ + * + 0x01 | 0x02 | 0x02 | up to 2^16 - 1 octets | + * +------+------+------+.......................+ + * + * Where: + * + * - "ver" is the protocol version number, here 1 to represent the + * first version of the SOCKS/GSS-API protocol + * + * - "mtyp" is the message type, here 2 to represent a protection + * -level negotiation message + * + * - "len" is the length of the "token" field in octets + * + * - "token" is the GSS-API encapsulated protection level + * + * The token is produced by encapsulating an octet containing the + * required protection level using gss_seal()/gss_wrap() with conf_req + * set to FALSE. The token is verified using gss_unseal()/ + * gss_unwrap(). + * + */ + + if(data->set.socks5_gssapi_nec) { + us_length = htons((short)1); + memcpy(socksreq + 2, &us_length, sizeof(short)); + } + else { + status = s_pSecFn->QueryContextAttributes(&sspi_context, + SECPKG_ATTR_SIZES, + &sspi_sizes); + if(check_sspi_err(data, status, "QueryContextAttributes")) { + s_pSecFn->DeleteSecurityContext(&sspi_context); + failf(data, "Failed to query security context attributes."); + return CURLE_COULDNT_CONNECT; + } + + sspi_w_token[0].cbBuffer = sspi_sizes.cbSecurityTrailer; + sspi_w_token[0].BufferType = SECBUFFER_TOKEN; + sspi_w_token[0].pvBuffer = malloc(sspi_sizes.cbSecurityTrailer); + + if(!sspi_w_token[0].pvBuffer) { + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + + sspi_w_token[1].cbBuffer = 1; + sspi_w_token[1].pvBuffer = malloc(1); + if(!sspi_w_token[1].pvBuffer) { + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + + memcpy(sspi_w_token[1].pvBuffer, &gss_enc, 1); + sspi_w_token[2].BufferType = SECBUFFER_PADDING; + sspi_w_token[2].cbBuffer = sspi_sizes.cbBlockSize; + sspi_w_token[2].pvBuffer = malloc(sspi_sizes.cbBlockSize); + if(!sspi_w_token[2].pvBuffer) { + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + status = s_pSecFn->EncryptMessage(&sspi_context, + KERB_WRAP_NO_ENCRYPT, + &wrap_desc, + 0); + if(check_sspi_err(data, status, "EncryptMessage")) { + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + failf(data, "Failed to query security context attributes."); + return CURLE_COULDNT_CONNECT; + } + sspi_send_token.cbBuffer = sspi_w_token[0].cbBuffer + + sspi_w_token[1].cbBuffer + + sspi_w_token[2].cbBuffer; + sspi_send_token.pvBuffer = malloc(sspi_send_token.cbBuffer); + if(!sspi_send_token.pvBuffer) { + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + + memcpy(sspi_send_token.pvBuffer, sspi_w_token[0].pvBuffer, + sspi_w_token[0].cbBuffer); + memcpy((PUCHAR) sspi_send_token.pvBuffer +(int)sspi_w_token[0].cbBuffer, + sspi_w_token[1].pvBuffer, sspi_w_token[1].cbBuffer); + memcpy((PUCHAR) sspi_send_token.pvBuffer + + sspi_w_token[0].cbBuffer + + sspi_w_token[1].cbBuffer, + sspi_w_token[2].pvBuffer, sspi_w_token[2].cbBuffer); + + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + sspi_w_token[0].pvBuffer = NULL; + sspi_w_token[0].cbBuffer = 0; + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + sspi_w_token[1].pvBuffer = NULL; + sspi_w_token[1].cbBuffer = 0; + s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + sspi_w_token[2].pvBuffer = NULL; + sspi_w_token[2].cbBuffer = 0; + + us_length = htons((short)sspi_send_token.cbBuffer); + memcpy(socksreq + 2, &us_length, sizeof(short)); + } + + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + if(code || (4 != written)) { + failf(data, "Failed to send SSPI encryption request."); + if(sspi_send_token.pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + if(data->set.socks5_gssapi_nec) { + memcpy(socksreq, &gss_enc, 1); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code); + if(code || (1 != written)) { + failf(data, "Failed to send SSPI encryption type."); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + } + else { + written = Curl_conn_cf_send(cf->next, data, + (char *)sspi_send_token.pvBuffer, + sspi_send_token.cbBuffer, &code); + if(code || (sspi_send_token.cbBuffer != (size_t)written)) { + failf(data, "Failed to send SSPI encryption type."); + if(sspi_send_token.pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + if(sspi_send_token.pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + } + + result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); + if(result || (actualread != 4)) { + failf(data, "Failed to receive SSPI encryption response."); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + /* ignore the first (VER) byte */ + if(socksreq[1] == 255) { /* status / message type */ + failf(data, "User was rejected by the SOCKS5 server (%u %u).", + (unsigned int)socksreq[0], (unsigned int)socksreq[1]); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + if(socksreq[1] != 2) { /* status / message type */ + failf(data, "Invalid SSPI encryption response type (%u %u).", + (unsigned int)socksreq[0], (unsigned int)socksreq[1]); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + memcpy(&us_length, socksreq + 2, sizeof(short)); + us_length = ntohs(us_length); + + sspi_w_token[0].cbBuffer = us_length; + sspi_w_token[0].pvBuffer = malloc(us_length); + if(!sspi_w_token[0].pvBuffer) { + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + + result = Curl_blockread_all(cf, data, (char *)sspi_w_token[0].pvBuffer, + sspi_w_token[0].cbBuffer, &actualread); + + if(result || (actualread != us_length)) { + failf(data, "Failed to receive SSPI encryption type."); + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + + if(!data->set.socks5_gssapi_nec) { + wrap_desc.cBuffers = 2; + sspi_w_token[0].BufferType = SECBUFFER_STREAM; + sspi_w_token[1].BufferType = SECBUFFER_DATA; + sspi_w_token[1].cbBuffer = 0; + sspi_w_token[1].pvBuffer = NULL; + + status = s_pSecFn->DecryptMessage(&sspi_context, + &wrap_desc, + 0, + &qop); + + if(check_sspi_err(data, status, "DecryptMessage")) { + if(sspi_w_token[0].pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + if(sspi_w_token[1].pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + failf(data, "Failed to query security context attributes."); + return CURLE_COULDNT_CONNECT; + } + + if(sspi_w_token[1].cbBuffer != 1) { + failf(data, "Invalid SSPI encryption response length (%lu).", + (unsigned long)sspi_w_token[1].cbBuffer); + if(sspi_w_token[0].pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + if(sspi_w_token[1].pvBuffer) + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + memcpy(socksreq, sspi_w_token[1].pvBuffer, sspi_w_token[1].cbBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + } + else { + if(sspi_w_token[0].cbBuffer != 1) { + failf(data, "Invalid SSPI encryption response length (%lu).", + (unsigned long)sspi_w_token[0].cbBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + memcpy(socksreq, sspi_w_token[0].pvBuffer, sspi_w_token[0].cbBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + } + (void)curlx_nonblock(sock, TRUE); + + infof(data, "SOCKS5 access with%s protection granted.", + (socksreq[0] == 0)?"out GSS-API data": + ((socksreq[0] == 1)?" GSS-API integrity":" GSS-API confidentiality")); + + /* For later use if encryption is required + conn->socks5_gssapi_enctype = socksreq[0]; + if(socksreq[0] != 0) + conn->socks5_sspi_context = sspi_context; + else { + s_pSecFn->DeleteSecurityContext(&sspi_context); + conn->socks5_sspi_context = sspi_context; + } + */ + return CURLE_OK; +} +#endif diff --git a/Utilities/cmcurl/lib/speedcheck.c b/Utilities/cmcurl/lib/speedcheck.c new file mode 100644 index 0000000..580efbd --- /dev/null +++ b/Utilities/cmcurl/lib/speedcheck.c @@ -0,0 +1,79 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> +#include "urldata.h" +#include "sendf.h" +#include "multiif.h" +#include "speedcheck.h" + +void Curl_speedinit(struct Curl_easy *data) +{ + memset(&data->state.keeps_speed, 0, sizeof(struct curltime)); +} + +/* + * @unittest: 1606 + */ +CURLcode Curl_speedcheck(struct Curl_easy *data, + struct curltime now) +{ + if(data->req.keepon & KEEP_RECV_PAUSE) + /* A paused transfer is not qualified for speed checks */ + return CURLE_OK; + + if((data->progress.current_speed >= 0) && data->set.low_speed_time) { + if(data->progress.current_speed < data->set.low_speed_limit) { + if(!data->state.keeps_speed.tv_sec) + /* under the limit at this very moment */ + data->state.keeps_speed = now; + else { + /* how long has it been under the limit */ + timediff_t howlong = Curl_timediff(now, data->state.keeps_speed); + + if(howlong >= data->set.low_speed_time * 1000) { + /* too long */ + failf(data, + "Operation too slow. " + "Less than %ld bytes/sec transferred the last %ld seconds", + data->set.low_speed_limit, + data->set.low_speed_time); + return CURLE_OPERATION_TIMEDOUT; + } + } + } + else + /* faster right now */ + data->state.keeps_speed.tv_sec = 0; + } + + if(data->set.low_speed_limit) + /* if 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); + + return CURLE_OK; +} diff --git a/Utilities/cmcurl/lib/speedcheck.h b/Utilities/cmcurl/lib/speedcheck.h new file mode 100644 index 0000000..bff2f32 --- /dev/null +++ b/Utilities/cmcurl/lib/speedcheck.h @@ -0,0 +1,35 @@ +#ifndef HEADER_CURL_SPEEDCHECK_H +#define HEADER_CURL_SPEEDCHECK_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "timeval.h" + +void Curl_speedinit(struct Curl_easy *data); +CURLcode Curl_speedcheck(struct Curl_easy *data, + struct curltime now); + +#endif /* HEADER_CURL_SPEEDCHECK_H */ diff --git a/Utilities/cmcurl/lib/splay.c b/Utilities/cmcurl/lib/splay.c new file mode 100644 index 0000000..48e079b --- /dev/null +++ b/Utilities/cmcurl/lib/splay.c @@ -0,0 +1,278 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#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 compare(i,j) Curl_splaycomparekeys((i),(j)) + +/* + * Splay using the key i (which may or may not be in the tree.) The starting + * root is t. + */ +struct Curl_tree *Curl_splay(struct curltime i, + struct Curl_tree *t) +{ + struct Curl_tree N, *l, *r, *y; + + if(!t) + return t; + N.smaller = N.larger = NULL; + l = r = &N; + + for(;;) { + long comp = compare(i, t->key); + if(comp < 0) { + if(!t->smaller) + break; + if(compare(i, t->smaller->key) < 0) { + y = t->smaller; /* rotate smaller */ + t->smaller = y->larger; + y->larger = t; + t = y; + if(!t->smaller) + break; + } + r->smaller = t; /* link smaller */ + r = t; + t = t->smaller; + } + else if(comp > 0) { + if(!t->larger) + break; + if(compare(i, t->larger->key) > 0) { + y = t->larger; /* rotate larger */ + t->larger = y->smaller; + y->smaller = t; + t = y; + if(!t->larger) + break; + } + l->larger = t; /* link larger */ + l = t; + t = t->larger; + } + else + break; + } + + l->larger = t->smaller; /* assemble */ + r->smaller = t->larger; + t->smaller = N.larger; + t->larger = N.smaller; + + return t; +} + +/* 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(struct curltime i, + struct Curl_tree *t, + struct Curl_tree *node) +{ + static const struct curltime KEY_NOTUSED = { + ~0, -1 + }; /* will *NEVER* appear */ + + if(!node) + return t; + + if(t) { + t = Curl_splay(i, t); + if(compare(i, t->key) == 0) { + /* There already exists a node in the tree with the very same key. Build + a doubly-linked circular list of nodes. We add the new 'node' struct + to the end of this list. */ + + node->key = KEY_NOTUSED; /* we set the key in the sub node to NOTUSED + to quickly 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 */ + } + } + + if(!t) { + node->smaller = node->larger = NULL; + } + else if(compare(i, t->key) < 0) { + node->smaller = t->smaller; + node->larger = t; + t->smaller = NULL; + + } + else { + node->larger = t->larger; + node->smaller = t; + t->larger = NULL; + } + node->key = i; + + /* 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(struct curltime i, + struct Curl_tree *t, + struct Curl_tree **removed) +{ + static const struct curltime tv_zero = {0, 0}; + struct Curl_tree *x; + + if(!t) { + *removed = NULL; /* none removed since there was no root */ + return NULL; + } + + /* find smallest */ + t = Curl_splay(tv_zero, t); + if(compare(i, t->key) < 0) { + /* even the smallest is too big */ + *removed = NULL; + return t; + } + + /* 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; + return x; /* new root */ + } + + /* we splayed the tree to the smallest element, there is no smaller */ + x = t->larger; + *removed = t; + + return x; +} + + +/* Deletes the very node we point out from the tree if it's there. Stores a + * pointer to the new resulting tree in 'newroot'. + * + * Returns zero on success and non-zero on errors! + * When returning error, it does not touch the 'newroot' pointer. + * + * NOTE: when the last node of the tree is removed, there's no tree left so + * 'newroot' will be made to point to NULL. + * + * @unittest: 1309 + */ +int Curl_splayremove(struct Curl_tree *t, + struct Curl_tree *removenode, + struct Curl_tree **newroot) +{ + static const struct curltime KEY_NOTUSED = { + ~0, -1 + }; /* will *NEVER* appear */ + struct Curl_tree *x; + + if(!t || !removenode) + return 1; + + if(compare(KEY_NOTUSED, removenode->key) == 0) { + /* Key set to NOTUSED means it is a subnode within a 'same' linked list + and thus we can unlink it easily. */ + if(removenode->samen == removenode) + /* A non-subnode should never be set to KEY_NOTUSED */ + 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); + + /* 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 + isn't actually in the tree. + + We cannot just compare the keys here as a double remove in quick + succession of a node with key != KEY_NOTUSED && same != NULL + could return the same key but a different node. */ + if(t != removenode) + return 2; + + /* Check if there is a list with identical sizes, as then we're trying to + remove the root node of a list of nodes with identical keys. */ + x = t->samen; + if(x != t) { + /* 'x' is the new root node, we just 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; + } + else { + /* Remove the root node */ + if(!t->smaller) + x = t->larger; + else { + x = Curl_splay(removenode->key, t->smaller); + x->larger = t->larger; + } + } + + *newroot = x; /* store new root pointer */ + + return 0; +} diff --git a/Utilities/cmcurl/lib/splay.h b/Utilities/cmcurl/lib/splay.h new file mode 100644 index 0000000..dd1d07a --- /dev/null +++ b/Utilities/cmcurl/lib/splay.h @@ -0,0 +1,58 @@ +#ifndef HEADER_CURL_SPLAY_H +#define HEADER_CURL_SPLAY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" +#include "timeval.h" + +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 *payload; /* data the splay code doesn't care about */ +}; + +struct Curl_tree *Curl_splay(struct curltime i, + struct Curl_tree *t); + +struct Curl_tree *Curl_splayinsert(struct curltime key, + struct Curl_tree *t, + struct Curl_tree *newnode); + +struct Curl_tree *Curl_splaygetbest(struct curltime key, + struct Curl_tree *t, + struct Curl_tree **removed); + +int Curl_splayremove(struct Curl_tree *t, + struct Curl_tree *removenode, + struct Curl_tree **newroot); + +#define Curl_splaycomparekeys(i,j) ( ((i.tv_sec) < (j.tv_sec)) ? -1 : \ + ( ((i.tv_sec) > (j.tv_sec)) ? 1 : \ + ( ((i.tv_usec) < (j.tv_usec)) ? -1 : \ + ( ((i.tv_usec) > (j.tv_usec)) ? 1 : 0)))) + +#endif /* HEADER_CURL_SPLAY_H */ diff --git a/Utilities/cmcurl/lib/strcase.c b/Utilities/cmcurl/lib/strcase.c new file mode 100644 index 0000000..7c0b4ef --- /dev/null +++ b/Utilities/cmcurl/lib/strcase.c @@ -0,0 +1,204 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#include "strcase.h" + +/* Mapping table to go from lowercase to uppercase for plain ASCII.*/ +static const unsigned char 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, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, +60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, +79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 65, +66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, +85, 86, 87, 88, 89, 90, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, +134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, +150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, +166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, +182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, +198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, +214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, +230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, +246, 247, 248, 249, 250, 251, 252, 253, 254, 255 +}; + +/* Mapping table to go from uppercase to lowercase for plain ASCII.*/ +static const unsigned char 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, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, +62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, +111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, +96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, +112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, +128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, +144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, +160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, +176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, +192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, +208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, +224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, +240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 +}; + + +/* Portable, consistent toupper. Do not use toupper() because its behavior is + altered by the current locale. */ +char Curl_raw_toupper(char in) +{ + return 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 tolowermap[(unsigned char) in]; +} + +/* + * curl_strequal() is for doing "raw" case insensitive strings. This is meant + * to be locale independent and only compare strings we know are safe for + * this. See https://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for + * further explanations as to why this function is necessary. + */ + +static int casecompare(const char *first, const char *second) +{ + while(*first && *second) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) + /* get out of the loop as soon as they don't match */ + return 0; + first++; + second++; + } + /* If we're here either the strings are the same or the length is different. + We can just test if the "current" character is non-zero for one and zero + for the other. Note that the characters may not be exactly the same even + if they match, we only want to compare zero-ness. */ + return !*first == !*second; +} + +/* --- public function --- */ +int curl_strequal(const char *first, const char *second) +{ + if(first && second) + /* both pointers point to something then compare them */ + return casecompare(first, second); + + /* if both pointers are NULL then treat them as equal */ + return (NULL == first && NULL == second); +} + +static int ncasecompare(const char *first, const char *second, size_t max) +{ + while(*first && *second && max) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) + return 0; + max--; + first++; + second++; + } + if(0 == max) + return 1; /* they are equal this far */ + + return Curl_raw_toupper(*first) == Curl_raw_toupper(*second); +} + +/* --- public function --- */ +int curl_strnequal(const char *first, const char *second, size_t max) +{ + if(first && second) + /* both pointers point to something then compare them */ + return ncasecompare(first, second, max); + + /* if both pointers are NULL then treat them as equal if max is non-zero */ + return (NULL == first && NULL == second && max); +} +/* 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 + * NUL-terminated if that limit is reached. + */ +void Curl_strntoupper(char *dest, const char *src, size_t n) +{ + if(n < 1) + return; + + do { + *dest++ = Curl_raw_toupper(*src); + } while(*src++ && --n); +} + +/* Copy a lower 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 + * NUL-terminated if that limit is reached. + */ +void Curl_strntolower(char *dest, const char *src, size_t n) +{ + if(n < 1) + return; + + do { + *dest++ = Curl_raw_tolower(*src); + } while(*src++ && --n); +} + +/* Compare case-sensitive NUL-terminated strings, taking care of possible + * null pointers. Return true if arguments match. + */ +bool Curl_safecmp(char *a, char *b) +{ + if(a && b) + return !strcmp(a, b); + return !a && !b; +} + +/* + * Curl_timestrcmp() returns 0 if the two strings are identical. The time this + * function spends is a function of the shortest string, not of the contents. + */ +int Curl_timestrcmp(const char *a, const char *b) +{ + int match = 0; + int i = 0; + + if(a && b) { + while(1) { + match |= a[i]^b[i]; + if(!a[i] || !b[i]) + break; + i++; + } + } + else + return a || b; + return match; +} diff --git a/Utilities/cmcurl/lib/strcase.h b/Utilities/cmcurl/lib/strcase.h new file mode 100644 index 0000000..8c50bbc --- /dev/null +++ b/Utilities/cmcurl/lib/strcase.h @@ -0,0 +1,54 @@ +#ifndef HEADER_CURL_STRCASE_H +#define HEADER_CURL_STRCASE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include <curl/curl.h> + +/* + * Only "raw" case insensitive strings. This is meant to be locale independent + * and only compare strings we know are safe for this. + * + * The function is capable of comparing a-z case insensitively. + * + * Result is 1 if text matches and 0 if not. + */ + +#define strcasecompare(a,b) curl_strequal(a,b) +#define strncasecompare(a,b,c) curl_strnequal(a,b,c) + +char Curl_raw_toupper(char in); +char Curl_raw_tolower(char in); + +/* checkprefix() is a shorter version of the above, used when the first + argument is the string literal */ +#define checkprefix(a,b) curl_strnequal(b, STRCONST(a)) + +void Curl_strntoupper(char *dest, const char *src, size_t n); +void Curl_strntolower(char *dest, const char *src, size_t n); + +bool Curl_safecmp(char *a, char *b); +int Curl_timestrcmp(const char *first, const char *second); + +#endif /* HEADER_CURL_STRCASE_H */ diff --git a/Utilities/cmcurl/lib/strdup.c b/Utilities/cmcurl/lib/strdup.c new file mode 100644 index 0000000..2578441 --- /dev/null +++ b/Utilities/cmcurl/lib/strdup.c @@ -0,0 +1,147 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include <curl/curl.h> + +#ifdef _WIN32 +#include <wchar.h> +#endif + +#include "strdup.h" +#include "curl_memory.h" + +/* The last #include file should be: */ +#include "memdebug.h" + +#ifndef HAVE_STRDUP +char *Curl_strdup(const char *str) +{ + size_t len; + char *newstr; + + if(!str) + return (char *)NULL; + + len = strlen(str) + 1; + + newstr = malloc(len); + if(!newstr) + return (char *)NULL; + + memcpy(newstr, str, len); + return newstr; +} +#endif + +#ifdef _WIN32 +/*************************************************************************** + * + * Curl_wcsdup(source) + * + * Copies the 'source' wchar string to a newly allocated buffer (that is + * returned). + * + * Returns the new pointer or NULL on failure. + * + ***************************************************************************/ +wchar_t *Curl_wcsdup(const wchar_t *src) +{ + size_t length = wcslen(src); + + if(length > (SIZE_T_MAX / sizeof(wchar_t)) - 1) + return (wchar_t *)NULL; /* integer overflow */ + + return (wchar_t *)Curl_memdup(src, (length + 1) * sizeof(wchar_t)); +} +#endif + +/*************************************************************************** + * + * Curl_memdup(source, length) + * + * Copies the 'source' data to a newly allocated buffer (that is + * returned). Copies 'length' bytes. + * + * Returns the new pointer or NULL on failure. + * + ***************************************************************************/ +void *Curl_memdup(const void *src, size_t length) +{ + void *buffer = malloc(length); + if(!buffer) + return NULL; /* fail */ + + memcpy(buffer, src, length); + + return buffer; +} + +/*************************************************************************** + * + * Curl_strndup(source, length) + * + * Copies the 'source' string to a newly allocated buffer (that is returned). + * Copies not more than 'length' bytes (up to a null terminator) then adds a + * null terminator. + * + * Returns the new pointer or NULL on failure. + * + ***************************************************************************/ +void *Curl_strndup(const char *src, size_t length) +{ + char *buf = memchr(src, '\0', length); + if(buf) + length = buf - src; + buf = malloc(length + 1); + if(!buf) + return NULL; + memcpy(buf, src, length); + buf[length] = 0; + return buf; +} + +/*************************************************************************** + * + * Curl_saferealloc(ptr, size) + * + * Does a normal realloc(), but will free the data pointer if the realloc + * fails. If 'size' is non-zero, it will free the data and return a failure. + * + * This convenience function is provided and used to help us avoid a common + * mistake pattern when we could pass in a zero, catch the NULL return and end + * up free'ing the memory twice. + * + * Returns the new pointer or NULL on failure. + * + ***************************************************************************/ +void *Curl_saferealloc(void *ptr, size_t size) +{ + void *datap = realloc(ptr, size); + if(size && !datap) + /* only free 'ptr' if size was non-zero */ + free(ptr); + return datap; +} diff --git a/Utilities/cmcurl/lib/strdup.h b/Utilities/cmcurl/lib/strdup.h new file mode 100644 index 0000000..9f12b25 --- /dev/null +++ b/Utilities/cmcurl/lib/strdup.h @@ -0,0 +1,38 @@ +#ifndef HEADER_CURL_STRDUP_H +#define HEADER_CURL_STRDUP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifndef HAVE_STRDUP +char *Curl_strdup(const char *str); +#endif +#ifdef _WIN32 +wchar_t* Curl_wcsdup(const wchar_t* src); +#endif +void *Curl_memdup(const void *src, size_t buffer_length); +void *Curl_saferealloc(void *ptr, size_t size); +void *Curl_strndup(const char *src, size_t length); + +#endif /* HEADER_CURL_STRDUP_H */ diff --git a/Utilities/cmcurl/lib/strerror.c b/Utilities/cmcurl/lib/strerror.c new file mode 100644 index 0000000..0d5f927 --- /dev/null +++ b/Utilities/cmcurl/lib/strerror.c @@ -0,0 +1,1116 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_STRERROR_R +# if (!defined(HAVE_POSIX_STRERROR_R) && \ + !defined(HAVE_GLIBC_STRERROR_R)) || \ + (defined(HAVE_POSIX_STRERROR_R) && defined(HAVE_GLIBC_STRERROR_R)) +# error "strerror_r MUST be either POSIX, glibc style" +# endif +#endif + +#include <curl/curl.h> + +#ifdef USE_LIBIDN2 +#include <idn2.h> +#endif + +#ifdef USE_WINDOWS_SSPI +#include "curl_sspi.h" +#endif + +#include "strerror.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#if defined(_WIN32) || defined(_WIN32_WCE) +#define PRESERVE_WINDOWS_ERROR_CODE +#endif + +const char * +curl_easy_strerror(CURLcode error) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + switch(error) { + case CURLE_OK: + return "No error"; + + case CURLE_UNSUPPORTED_PROTOCOL: + return "Unsupported protocol"; + + case CURLE_FAILED_INIT: + return "Failed initialization"; + + case CURLE_URL_MALFORMAT: + return "URL using bad/illegal format or missing URL"; + + case CURLE_NOT_BUILT_IN: + return "A requested feature, protocol or option was not found built-in in" + " this libcurl due to a build-time decision."; + + case CURLE_COULDNT_RESOLVE_PROXY: + return "Couldn't resolve proxy name"; + + case CURLE_COULDNT_RESOLVE_HOST: + return "Couldn't resolve host name"; + + case CURLE_COULDNT_CONNECT: + return "Couldn't connect to server"; + + case CURLE_WEIRD_SERVER_REPLY: + return "Weird server reply"; + + case CURLE_REMOTE_ACCESS_DENIED: + return "Access denied to remote resource"; + + case CURLE_FTP_ACCEPT_FAILED: + return "FTP: The server failed to connect to data port"; + + case CURLE_FTP_ACCEPT_TIMEOUT: + return "FTP: Accepting server connect has timed out"; + + case CURLE_FTP_PRET_FAILED: + return "FTP: The server did not accept the PRET command."; + + case CURLE_FTP_WEIRD_PASS_REPLY: + return "FTP: unknown PASS reply"; + + case CURLE_FTP_WEIRD_PASV_REPLY: + return "FTP: unknown PASV reply"; + + case CURLE_FTP_WEIRD_227_FORMAT: + return "FTP: unknown 227 response format"; + + case CURLE_FTP_CANT_GET_HOST: + return "FTP: can't figure out the host in the PASV response"; + + case CURLE_HTTP2: + return "Error in the HTTP2 framing layer"; + + case CURLE_FTP_COULDNT_SET_TYPE: + return "FTP: couldn't set file type"; + + case CURLE_PARTIAL_FILE: + return "Transferred a partial file"; + + case CURLE_FTP_COULDNT_RETR_FILE: + return "FTP: couldn't retrieve (RETR failed) the specified file"; + + case CURLE_QUOTE_ERROR: + return "Quote command returned error"; + + case CURLE_HTTP_RETURNED_ERROR: + return "HTTP response code said error"; + + case CURLE_WRITE_ERROR: + return "Failed writing received data to disk/application"; + + case CURLE_UPLOAD_FAILED: + return "Upload failed (at start/before it took off)"; + + case CURLE_READ_ERROR: + return "Failed to open/read local data from file/application"; + + case CURLE_OUT_OF_MEMORY: + return "Out of memory"; + + case CURLE_OPERATION_TIMEDOUT: + return "Timeout was reached"; + + case CURLE_FTP_PORT_FAILED: + return "FTP: command PORT failed"; + + case CURLE_FTP_COULDNT_USE_REST: + return "FTP: command REST failed"; + + case CURLE_RANGE_ERROR: + return "Requested range was not delivered by the server"; + + case CURLE_HTTP_POST_ERROR: + return "Internal problem setting up the POST"; + + case CURLE_SSL_CONNECT_ERROR: + return "SSL connect error"; + + case CURLE_BAD_DOWNLOAD_RESUME: + return "Couldn't resume download"; + + case CURLE_FILE_COULDNT_READ_FILE: + return "Couldn't read a file:// file"; + + case CURLE_LDAP_CANNOT_BIND: + return "LDAP: cannot bind"; + + case CURLE_LDAP_SEARCH_FAILED: + return "LDAP: search failed"; + + case CURLE_FUNCTION_NOT_FOUND: + return "A required function in the library was not found"; + + case CURLE_ABORTED_BY_CALLBACK: + return "Operation was aborted by an application callback"; + + case CURLE_BAD_FUNCTION_ARGUMENT: + return "A libcurl function was given a bad argument"; + + case CURLE_INTERFACE_FAILED: + return "Failed binding local connection end"; + + case CURLE_TOO_MANY_REDIRECTS: + return "Number of redirects hit maximum amount"; + + case CURLE_UNKNOWN_OPTION: + return "An unknown option was passed in to libcurl"; + + case CURLE_SETOPT_OPTION_SYNTAX: + return "Malformed option provided in a setopt"; + + case CURLE_GOT_NOTHING: + return "Server returned nothing (no headers, no data)"; + + case CURLE_SSL_ENGINE_NOTFOUND: + return "SSL crypto engine not found"; + + case CURLE_SSL_ENGINE_SETFAILED: + return "Can not set SSL crypto engine as default"; + + case CURLE_SSL_ENGINE_INITFAILED: + return "Failed to initialise SSL crypto engine"; + + case CURLE_SEND_ERROR: + return "Failed sending data to the peer"; + + case CURLE_RECV_ERROR: + return "Failure when receiving data from the peer"; + + case CURLE_SSL_CERTPROBLEM: + return "Problem with the local SSL certificate"; + + case CURLE_SSL_CIPHER: + return "Couldn't use specified SSL cipher"; + + case CURLE_PEER_FAILED_VERIFICATION: + return "SSL peer certificate or SSH remote key was not OK"; + + case CURLE_SSL_CACERT_BADFILE: + return "Problem with the SSL CA cert (path? access rights?)"; + + case CURLE_BAD_CONTENT_ENCODING: + return "Unrecognized or bad HTTP Content or Transfer-Encoding"; + + case CURLE_FILESIZE_EXCEEDED: + return "Maximum file size exceeded"; + + case CURLE_USE_SSL_FAILED: + return "Requested SSL level failed"; + + case CURLE_SSL_SHUTDOWN_FAILED: + return "Failed to shut down the SSL connection"; + + case CURLE_SSL_CRL_BADFILE: + return "Failed to load CRL file (path? access rights?, format?)"; + + case CURLE_SSL_ISSUER_ERROR: + return "Issuer check against peer certificate failed"; + + case CURLE_SEND_FAIL_REWIND: + return "Send failed since rewinding of the data stream failed"; + + case CURLE_LOGIN_DENIED: + return "Login denied"; + + case CURLE_TFTP_NOTFOUND: + return "TFTP: File Not Found"; + + case CURLE_TFTP_PERM: + return "TFTP: Access Violation"; + + case CURLE_REMOTE_DISK_FULL: + return "Disk full or allocation exceeded"; + + case CURLE_TFTP_ILLEGAL: + return "TFTP: Illegal operation"; + + case CURLE_TFTP_UNKNOWNID: + return "TFTP: Unknown transfer ID"; + + case CURLE_REMOTE_FILE_EXISTS: + return "Remote file already exists"; + + case CURLE_TFTP_NOSUCHUSER: + return "TFTP: No such user"; + + case CURLE_REMOTE_FILE_NOT_FOUND: + return "Remote file not found"; + + case CURLE_SSH: + return "Error in the SSH layer"; + + case CURLE_AGAIN: + return "Socket not ready for send/recv"; + + case CURLE_RTSP_CSEQ_ERROR: + return "RTSP CSeq mismatch or invalid CSeq"; + + case CURLE_RTSP_SESSION_ERROR: + return "RTSP session error"; + + case CURLE_FTP_BAD_FILE_LIST: + return "Unable to parse FTP file list"; + + case CURLE_CHUNK_FAILED: + return "Chunk callback failed"; + + case CURLE_NO_CONNECTION_AVAILABLE: + return "The max connection limit is reached"; + + case CURLE_SSL_PINNEDPUBKEYNOTMATCH: + return "SSL public key does not match pinned public key"; + + case CURLE_SSL_INVALIDCERTSTATUS: + return "SSL server certificate status verification FAILED"; + + case CURLE_HTTP2_STREAM: + return "Stream error in the HTTP/2 framing layer"; + + case CURLE_RECURSIVE_API_CALL: + return "API function called from within callback"; + + case CURLE_AUTH_ERROR: + return "An authentication function returned an error"; + + case CURLE_HTTP3: + return "HTTP/3 error"; + + case CURLE_QUIC_CONNECT_ERROR: + return "QUIC connection error"; + + case CURLE_PROXY: + return "proxy handshake error"; + + case CURLE_SSL_CLIENTCERT: + return "SSL Client Certificate required"; + + case CURLE_UNRECOVERABLE_POLL: + return "Unrecoverable error in select/poll"; + + /* error codes not used by current libcurl */ + case CURLE_OBSOLETE20: + case CURLE_OBSOLETE24: + case CURLE_OBSOLETE29: + case CURLE_OBSOLETE32: + case CURLE_OBSOLETE40: + case CURLE_OBSOLETE44: + case CURLE_OBSOLETE46: + case CURLE_OBSOLETE50: + case CURLE_OBSOLETE51: + case CURLE_OBSOLETE57: + case CURLE_OBSOLETE62: + case CURLE_OBSOLETE75: + case CURLE_OBSOLETE76: + case CURL_LAST: + break; + } + /* + * By using a switch, gcc -Wall will complain about enum values + * which do not appear, helping keep this function up-to-date. + * By using gcc -Wall -Werror, you can't forget. + * + * A table would not have the same benefit. Most compilers will + * generate code very similar to a table in any case, so there + * is little performance gain from a table. And something is broken + * for the user's application, anyways, so does it matter how fast + * it _doesn't_ work? + * + * The line number for the error will be near this comment, which + * is why it is here, and not at the start of the switch. + */ + return "Unknown error"; +#else + if(!error) + return "No error"; + else + return "Error"; +#endif +} + +const char * +curl_multi_strerror(CURLMcode error) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + switch(error) { + case CURLM_CALL_MULTI_PERFORM: + return "Please call curl_multi_perform() soon"; + + case CURLM_OK: + return "No error"; + + case CURLM_BAD_HANDLE: + return "Invalid multi handle"; + + case CURLM_BAD_EASY_HANDLE: + return "Invalid easy handle"; + + case CURLM_OUT_OF_MEMORY: + return "Out of memory"; + + case CURLM_INTERNAL_ERROR: + return "Internal error"; + + case CURLM_BAD_SOCKET: + return "Invalid socket argument"; + + case CURLM_UNKNOWN_OPTION: + return "Unknown option"; + + case CURLM_ADDED_ALREADY: + return "The easy handle is already added to a multi handle"; + + case CURLM_RECURSIVE_API_CALL: + return "API function called from within callback"; + + case CURLM_WAKEUP_FAILURE: + return "Wakeup is unavailable or failed"; + + case CURLM_BAD_FUNCTION_ARGUMENT: + return "A libcurl function was given a bad argument"; + + case CURLM_ABORTED_BY_CALLBACK: + return "Operation was aborted by an application callback"; + + case CURLM_UNRECOVERABLE_POLL: + return "Unrecoverable error in select/poll"; + + case CURLM_LAST: + break; + } + + return "Unknown error"; +#else + if(error == CURLM_OK) + return "No error"; + else + return "Error"; +#endif +} + +const char * +curl_share_strerror(CURLSHcode error) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + switch(error) { + case CURLSHE_OK: + return "No error"; + + case CURLSHE_BAD_OPTION: + return "Unknown share option"; + + case CURLSHE_IN_USE: + return "Share currently in use"; + + case CURLSHE_INVALID: + return "Invalid share handle"; + + case CURLSHE_NOMEM: + return "Out of memory"; + + case CURLSHE_NOT_BUILT_IN: + return "Feature not enabled in this library"; + + case CURLSHE_LAST: + break; + } + + return "CURLSHcode unknown"; +#else + if(error == CURLSHE_OK) + return "No error"; + else + return "Error"; +#endif +} + +const char * +curl_url_strerror(CURLUcode error) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + switch(error) { + case CURLUE_OK: + return "No error"; + + case CURLUE_BAD_HANDLE: + return "An invalid CURLU pointer was passed as argument"; + + case CURLUE_BAD_PARTPOINTER: + return "An invalid 'part' argument was passed as argument"; + + case CURLUE_MALFORMED_INPUT: + return "Malformed input to a URL function"; + + case CURLUE_BAD_PORT_NUMBER: + return "Port number was not a decimal number between 0 and 65535"; + + case CURLUE_UNSUPPORTED_SCHEME: + return "Unsupported URL scheme"; + + case CURLUE_URLDECODE: + return "URL decode error, most likely because of rubbish in the input"; + + case CURLUE_OUT_OF_MEMORY: + return "A memory function failed"; + + case CURLUE_USER_NOT_ALLOWED: + return "Credentials was passed in the URL when prohibited"; + + case CURLUE_UNKNOWN_PART: + return "An unknown part ID was passed to a URL API function"; + + case CURLUE_NO_SCHEME: + return "No scheme part in the URL"; + + case CURLUE_NO_USER: + return "No user part in the URL"; + + case CURLUE_NO_PASSWORD: + return "No password part in the URL"; + + case CURLUE_NO_OPTIONS: + return "No options part in the URL"; + + case CURLUE_NO_HOST: + return "No host part in the URL"; + + case CURLUE_NO_PORT: + return "No port part in the URL"; + + case CURLUE_NO_QUERY: + return "No query part in the URL"; + + case CURLUE_NO_FRAGMENT: + return "No fragment part in the URL"; + + case CURLUE_NO_ZONEID: + return "No zoneid part in the URL"; + + case CURLUE_BAD_LOGIN: + return "Bad login part"; + + case CURLUE_BAD_IPV6: + return "Bad IPv6 address"; + + case CURLUE_BAD_HOSTNAME: + return "Bad hostname"; + + case CURLUE_BAD_FILE_URL: + return "Bad file:// URL"; + + case CURLUE_BAD_SLASHES: + return "Unsupported number of slashes following scheme"; + + case CURLUE_BAD_SCHEME: + return "Bad scheme"; + + case CURLUE_BAD_PATH: + return "Bad path"; + + case CURLUE_BAD_FRAGMENT: + return "Bad fragment"; + + case CURLUE_BAD_QUERY: + return "Bad query"; + + case CURLUE_BAD_PASSWORD: + return "Bad password"; + + case CURLUE_BAD_USER: + return "Bad user"; + + case CURLUE_LACKS_IDN: + return "libcurl lacks IDN support"; + + case CURLUE_LAST: + break; + } + + return "CURLUcode unknown"; +#else + if(error == CURLUE_OK) + return "No error"; + else + return "Error"; +#endif +} + +#ifdef USE_WINSOCK +/* This is a helper function for Curl_strerror that converts Winsock error + * codes (WSAGetLastError) to error messages. + * Returns NULL if no error message was found for error code. + */ +static const char * +get_winsock_error (int err, char *buf, size_t len) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + const char *p; +#endif + + if(!len) + return NULL; + + *buf = '\0'; + +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)err; + return NULL; +#else + switch(err) { + case WSAEINTR: + p = "Call interrupted"; + break; + case WSAEBADF: + p = "Bad file"; + break; + case WSAEACCES: + p = "Bad access"; + break; + case WSAEFAULT: + p = "Bad argument"; + break; + case WSAEINVAL: + p = "Invalid arguments"; + break; + case WSAEMFILE: + p = "Out of file descriptors"; + break; + case WSAEWOULDBLOCK: + p = "Call would block"; + break; + case WSAEINPROGRESS: + case WSAEALREADY: + p = "Blocking call in progress"; + break; + case WSAENOTSOCK: + p = "Descriptor is not a socket"; + break; + case WSAEDESTADDRREQ: + p = "Need destination address"; + break; + case WSAEMSGSIZE: + p = "Bad message size"; + break; + case WSAEPROTOTYPE: + p = "Bad protocol"; + break; + case WSAENOPROTOOPT: + p = "Protocol option is unsupported"; + break; + case WSAEPROTONOSUPPORT: + p = "Protocol is unsupported"; + break; + case WSAESOCKTNOSUPPORT: + p = "Socket is unsupported"; + break; + case WSAEOPNOTSUPP: + p = "Operation not supported"; + break; + case WSAEAFNOSUPPORT: + p = "Address family not supported"; + break; + case WSAEPFNOSUPPORT: + p = "Protocol family not supported"; + break; + case WSAEADDRINUSE: + p = "Address already in use"; + break; + case WSAEADDRNOTAVAIL: + p = "Address not available"; + break; + case WSAENETDOWN: + p = "Network down"; + break; + case WSAENETUNREACH: + p = "Network unreachable"; + break; + case WSAENETRESET: + p = "Network has been reset"; + break; + case WSAECONNABORTED: + p = "Connection was aborted"; + break; + case WSAECONNRESET: + p = "Connection was reset"; + break; + case WSAENOBUFS: + p = "No buffer space"; + break; + case WSAEISCONN: + p = "Socket is already connected"; + break; + case WSAENOTCONN: + p = "Socket is not connected"; + break; + case WSAESHUTDOWN: + p = "Socket has been shut down"; + break; + case WSAETOOMANYREFS: + p = "Too many references"; + break; + case WSAETIMEDOUT: + p = "Timed out"; + break; + case WSAECONNREFUSED: + p = "Connection refused"; + break; + case WSAELOOP: + p = "Loop??"; + break; + case WSAENAMETOOLONG: + p = "Name too long"; + break; + case WSAEHOSTDOWN: + p = "Host down"; + break; + case WSAEHOSTUNREACH: + p = "Host unreachable"; + break; + case WSAENOTEMPTY: + p = "Not empty"; + break; + case WSAEPROCLIM: + p = "Process limit reached"; + break; + case WSAEUSERS: + p = "Too many users"; + break; + case WSAEDQUOT: + p = "Bad quota"; + break; + case WSAESTALE: + p = "Something is stale"; + break; + case WSAEREMOTE: + p = "Remote error"; + break; +#ifdef WSAEDISCON /* missing in SalfordC! */ + case WSAEDISCON: + p = "Disconnected"; + break; +#endif + /* Extended Winsock errors */ + case WSASYSNOTREADY: + p = "Winsock library is not ready"; + break; + case WSANOTINITIALISED: + p = "Winsock library not initialised"; + break; + case WSAVERNOTSUPPORTED: + p = "Winsock version not supported"; + break; + + /* getXbyY() errors (already handled in herrmsg): + * Authoritative Answer: Host not found */ + case WSAHOST_NOT_FOUND: + p = "Host not found"; + break; + + /* Non-Authoritative: Host not found, or SERVERFAIL */ + case WSATRY_AGAIN: + p = "Host not found, try again"; + break; + + /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */ + case WSANO_RECOVERY: + p = "Unrecoverable error in call to nameserver"; + break; + + /* Valid name, no data record of requested type */ + case WSANO_DATA: + p = "No data record of requested type"; + break; + + default: + return NULL; + } + strncpy(buf, p, len); + buf [len-1] = '\0'; + return buf; +#endif +} +#endif /* USE_WINSOCK */ + +#if defined(_WIN32) || defined(_WIN32_WCE) +/* This is a helper function for Curl_strerror that converts Windows API error + * codes (GetLastError) to error messages. + * Returns NULL if no error message was found for error code. + */ +static const char * +get_winapi_error(int err, char *buf, size_t buflen) +{ + char *p; + wchar_t wbuf[256]; + + if(!buflen) + return NULL; + + *buf = '\0'; + *wbuf = L'\0'; + + /* We return the local codepage version of the error string because if it is + output to the user's terminal it will likely be with functions which + expect the local codepage (eg fprintf, failf, infof). + FormatMessageW -> wcstombs is used for Windows CE compatibility. */ + if(FormatMessageW((FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS), NULL, err, + LANG_NEUTRAL, wbuf, sizeof(wbuf)/sizeof(wchar_t), NULL)) { + size_t written = wcstombs(buf, wbuf, buflen - 1); + if(written != (size_t)-1) + buf[written] = '\0'; + else + *buf = '\0'; + } + + /* Truncate multiple lines */ + p = strchr(buf, '\n'); + if(p) { + if(p > buf && *(p-1) == '\r') + *(p-1) = '\0'; + else + *p = '\0'; + } + + return (*buf ? buf : NULL); +} +#endif /* _WIN32 || _WIN32_WCE */ + +/* + * Our thread-safe and smart strerror() replacement. + * + * The 'err' argument passed in to this function MUST be a true errno number + * as reported on this system. We do no range checking on the number before + * we pass it to the "number-to-message" conversion function and there might + * be systems that don't do proper range checking in there themselves. + * + * We don't do range checking (on systems other than Windows) since there is + * no good reliable and portable way to do it. + * + * On Windows different types of error codes overlap. This function has an + * order of preference when trying to match error codes: + * CRT (errno), Winsock (WSAGetLastError), Windows API (GetLastError). + * + * It may be more correct to call one of the variant functions instead: + * Call Curl_sspi_strerror if the error code is definitely Windows SSPI. + * Call Curl_winapi_strerror if the error code is definitely Windows API. + */ +const char *Curl_strerror(int err, char *buf, size_t buflen) +{ +#ifdef PRESERVE_WINDOWS_ERROR_CODE + DWORD old_win_err = GetLastError(); +#endif + int old_errno = errno; + char *p; + size_t max; + + if(!buflen) + return NULL; + +#ifndef _WIN32 + DEBUGASSERT(err >= 0); +#endif + + max = buflen - 1; + *buf = '\0'; + +#if defined(_WIN32) || defined(_WIN32_WCE) +#if defined(_WIN32) + /* 'sys_nerr' is the maximum errno number, it is not widely portable */ + if(err >= 0 && err < sys_nerr) + strncpy(buf, sys_errlist[err], max); + else +#endif + { + if( +#ifdef USE_WINSOCK + !get_winsock_error(err, buf, max) && +#endif + !get_winapi_error((DWORD)err, buf, max)) + msnprintf(buf, max, "Unknown error %d (%#x)", err, err); + } +#else /* not Windows coming up */ + +#if defined(HAVE_STRERROR_R) && defined(HAVE_POSIX_STRERROR_R) + /* + * The POSIX-style strerror_r() may set errno to ERANGE if insufficient + * storage is supplied via 'strerrbuf' and 'buflen' to hold the generated + * message string, or EINVAL if 'errnum' is not a valid error number. + */ + if(0 != strerror_r(err, buf, max)) { + if('\0' == buf[0]) + msnprintf(buf, max, "Unknown error %d", err); + } +#elif defined(HAVE_STRERROR_R) && defined(HAVE_GLIBC_STRERROR_R) + /* + * The glibc-style strerror_r() only *might* use the buffer we pass to + * the function, but it always returns the error message as a pointer, + * so we must copy that string unconditionally (if non-NULL). + */ + { + char buffer[256]; + char *msg = strerror_r(err, buffer, sizeof(buffer)); + if(msg) + strncpy(buf, msg, max); + else + msnprintf(buf, max, "Unknown error %d", err); + } +#else + { + /* !checksrc! disable STRERROR 1 */ + const char *msg = strerror(err); + if(msg) + strncpy(buf, msg, max); + else + msnprintf(buf, max, "Unknown error %d", err); + } +#endif + +#endif /* end of not Windows */ + + buf[max] = '\0'; /* make sure the string is null-terminated */ + + /* strip trailing '\r\n' or '\n'. */ + p = strrchr(buf, '\n'); + if(p && (p - buf) >= 2) + *p = '\0'; + p = strrchr(buf, '\r'); + if(p && (p - buf) >= 1) + *p = '\0'; + + if(errno != old_errno) + errno = old_errno; + +#ifdef PRESERVE_WINDOWS_ERROR_CODE + if(old_win_err != GetLastError()) + SetLastError(old_win_err); +#endif + + return buf; +} + +/* + * Curl_winapi_strerror: + * Variant of Curl_strerror if the error code is definitely Windows API. + */ +#if defined(_WIN32) || defined(_WIN32_WCE) +const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen) +{ +#ifdef PRESERVE_WINDOWS_ERROR_CODE + DWORD old_win_err = GetLastError(); +#endif + int old_errno = errno; + + if(!buflen) + return NULL; + + *buf = '\0'; + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(!get_winapi_error(err, buf, buflen)) { + msnprintf(buf, buflen, "Unknown error %lu (0x%08lX)", err, err); + } +#else + { + const char *txt = (err == ERROR_SUCCESS) ? "No error" : "Error"; + strncpy(buf, txt, buflen); + buf[buflen - 1] = '\0'; + } +#endif + + if(errno != old_errno) + errno = old_errno; + +#ifdef PRESERVE_WINDOWS_ERROR_CODE + if(old_win_err != GetLastError()) + SetLastError(old_win_err); +#endif + + return buf; +} +#endif /* _WIN32 || _WIN32_WCE */ + +#ifdef USE_WINDOWS_SSPI +/* + * Curl_sspi_strerror: + * Variant of Curl_strerror if the error code is definitely Windows SSPI. + */ +const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) +{ +#ifdef PRESERVE_WINDOWS_ERROR_CODE + DWORD old_win_err = GetLastError(); +#endif + int old_errno = errno; + const char *txt; + + if(!buflen) + return NULL; + + *buf = '\0'; + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + + switch(err) { + case SEC_E_OK: + txt = "No error"; + break; +#define SEC2TXT(sec) case sec: txt = #sec; break + SEC2TXT(CRYPT_E_REVOKED); + SEC2TXT(CRYPT_E_NO_REVOCATION_DLL); + SEC2TXT(CRYPT_E_NO_REVOCATION_CHECK); + SEC2TXT(CRYPT_E_REVOCATION_OFFLINE); + SEC2TXT(CRYPT_E_NOT_IN_REVOCATION_DATABASE); + SEC2TXT(SEC_E_ALGORITHM_MISMATCH); + SEC2TXT(SEC_E_BAD_BINDINGS); + SEC2TXT(SEC_E_BAD_PKGID); + SEC2TXT(SEC_E_BUFFER_TOO_SMALL); + SEC2TXT(SEC_E_CANNOT_INSTALL); + SEC2TXT(SEC_E_CANNOT_PACK); + SEC2TXT(SEC_E_CERT_EXPIRED); + SEC2TXT(SEC_E_CERT_UNKNOWN); + SEC2TXT(SEC_E_CERT_WRONG_USAGE); + SEC2TXT(SEC_E_CONTEXT_EXPIRED); + SEC2TXT(SEC_E_CROSSREALM_DELEGATION_FAILURE); + SEC2TXT(SEC_E_CRYPTO_SYSTEM_INVALID); + SEC2TXT(SEC_E_DECRYPT_FAILURE); + SEC2TXT(SEC_E_DELEGATION_POLICY); + SEC2TXT(SEC_E_DELEGATION_REQUIRED); + SEC2TXT(SEC_E_DOWNGRADE_DETECTED); + SEC2TXT(SEC_E_ENCRYPT_FAILURE); + SEC2TXT(SEC_E_ILLEGAL_MESSAGE); + SEC2TXT(SEC_E_INCOMPLETE_CREDENTIALS); + SEC2TXT(SEC_E_INCOMPLETE_MESSAGE); + SEC2TXT(SEC_E_INSUFFICIENT_MEMORY); + SEC2TXT(SEC_E_INTERNAL_ERROR); + SEC2TXT(SEC_E_INVALID_HANDLE); + SEC2TXT(SEC_E_INVALID_PARAMETER); + SEC2TXT(SEC_E_INVALID_TOKEN); + SEC2TXT(SEC_E_ISSUING_CA_UNTRUSTED); + SEC2TXT(SEC_E_ISSUING_CA_UNTRUSTED_KDC); + SEC2TXT(SEC_E_KDC_CERT_EXPIRED); + SEC2TXT(SEC_E_KDC_CERT_REVOKED); + SEC2TXT(SEC_E_KDC_INVALID_REQUEST); + SEC2TXT(SEC_E_KDC_UNABLE_TO_REFER); + SEC2TXT(SEC_E_KDC_UNKNOWN_ETYPE); + SEC2TXT(SEC_E_LOGON_DENIED); + SEC2TXT(SEC_E_MAX_REFERRALS_EXCEEDED); + SEC2TXT(SEC_E_MESSAGE_ALTERED); + SEC2TXT(SEC_E_MULTIPLE_ACCOUNTS); + SEC2TXT(SEC_E_MUST_BE_KDC); + SEC2TXT(SEC_E_NOT_OWNER); + SEC2TXT(SEC_E_NO_AUTHENTICATING_AUTHORITY); + SEC2TXT(SEC_E_NO_CREDENTIALS); + SEC2TXT(SEC_E_NO_IMPERSONATION); + SEC2TXT(SEC_E_NO_IP_ADDRESSES); + SEC2TXT(SEC_E_NO_KERB_KEY); + SEC2TXT(SEC_E_NO_PA_DATA); + SEC2TXT(SEC_E_NO_S4U_PROT_SUPPORT); + SEC2TXT(SEC_E_NO_TGT_REPLY); + SEC2TXT(SEC_E_OUT_OF_SEQUENCE); + SEC2TXT(SEC_E_PKINIT_CLIENT_FAILURE); + SEC2TXT(SEC_E_PKINIT_NAME_MISMATCH); + SEC2TXT(SEC_E_POLICY_NLTM_ONLY); + SEC2TXT(SEC_E_QOP_NOT_SUPPORTED); + SEC2TXT(SEC_E_REVOCATION_OFFLINE_C); + SEC2TXT(SEC_E_REVOCATION_OFFLINE_KDC); + SEC2TXT(SEC_E_SECPKG_NOT_FOUND); + SEC2TXT(SEC_E_SECURITY_QOS_FAILED); + SEC2TXT(SEC_E_SHUTDOWN_IN_PROGRESS); + SEC2TXT(SEC_E_SMARTCARD_CERT_EXPIRED); + SEC2TXT(SEC_E_SMARTCARD_CERT_REVOKED); + SEC2TXT(SEC_E_SMARTCARD_LOGON_REQUIRED); + SEC2TXT(SEC_E_STRONG_CRYPTO_NOT_SUPPORTED); + SEC2TXT(SEC_E_TARGET_UNKNOWN); + SEC2TXT(SEC_E_TIME_SKEW); + SEC2TXT(SEC_E_TOO_MANY_PRINCIPALS); + SEC2TXT(SEC_E_UNFINISHED_CONTEXT_DELETED); + SEC2TXT(SEC_E_UNKNOWN_CREDENTIALS); + SEC2TXT(SEC_E_UNSUPPORTED_FUNCTION); + SEC2TXT(SEC_E_UNSUPPORTED_PREAUTH); + SEC2TXT(SEC_E_UNTRUSTED_ROOT); + SEC2TXT(SEC_E_WRONG_CREDENTIAL_HANDLE); + SEC2TXT(SEC_E_WRONG_PRINCIPAL); + SEC2TXT(SEC_I_COMPLETE_AND_CONTINUE); + SEC2TXT(SEC_I_COMPLETE_NEEDED); + SEC2TXT(SEC_I_CONTEXT_EXPIRED); + SEC2TXT(SEC_I_CONTINUE_NEEDED); + SEC2TXT(SEC_I_INCOMPLETE_CREDENTIALS); + SEC2TXT(SEC_I_LOCAL_LOGON); + SEC2TXT(SEC_I_NO_LSA_CONTEXT); + SEC2TXT(SEC_I_RENEGOTIATE); + SEC2TXT(SEC_I_SIGNATURE_NEEDED); + default: + txt = "Unknown error"; + } + + if(err == SEC_E_ILLEGAL_MESSAGE) { + msnprintf(buf, buflen, + "SEC_E_ILLEGAL_MESSAGE (0x%08X) - This error usually occurs " + "when a fatal SSL/TLS alert is received (e.g. handshake failed)." + " More detail may be available in the Windows System event log.", + err); + } + else { + char txtbuf[80]; + char msgbuf[256]; + + msnprintf(txtbuf, sizeof(txtbuf), "%s (0x%08X)", txt, err); + + if(get_winapi_error(err, msgbuf, sizeof(msgbuf))) + msnprintf(buf, buflen, "%s - %s", txtbuf, msgbuf); + else { + strncpy(buf, txtbuf, buflen); + buf[buflen - 1] = '\0'; + } + } + +#else + if(err == SEC_E_OK) + txt = "No error"; + else + txt = "Error"; + strncpy(buf, txt, buflen); + buf[buflen - 1] = '\0'; +#endif + + if(errno != old_errno) + errno = old_errno; + +#ifdef PRESERVE_WINDOWS_ERROR_CODE + if(old_win_err != GetLastError()) + SetLastError(old_win_err); +#endif + + return buf; +} +#endif /* USE_WINDOWS_SSPI */ diff --git a/Utilities/cmcurl/lib/strerror.h b/Utilities/cmcurl/lib/strerror.h new file mode 100644 index 0000000..6806867 --- /dev/null +++ b/Utilities/cmcurl/lib/strerror.h @@ -0,0 +1,39 @@ +#ifndef HEADER_CURL_STRERROR_H +#define HEADER_CURL_STRERROR_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "urldata.h" + +#define STRERROR_LEN 256 /* a suitable length */ + +const char *Curl_strerror(int err, char *buf, size_t buflen); +#if defined(_WIN32) || defined(_WIN32_WCE) +const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen); +#endif +#ifdef USE_WINDOWS_SSPI +const char *Curl_sspi_strerror(int err, char *buf, size_t buflen); +#endif + +#endif /* HEADER_CURL_STRERROR_H */ diff --git a/Utilities/cmcurl/lib/strtok.c b/Utilities/cmcurl/lib/strtok.c new file mode 100644 index 0000000..d8e1e81 --- /dev/null +++ b/Utilities/cmcurl/lib/strtok.c @@ -0,0 +1,68 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef HAVE_STRTOK_R +#include <stddef.h> + +#include "strtok.h" + +char * +Curl_strtok_r(char *ptr, const char *sep, char **end) +{ + if(!ptr) + /* we got NULL input so then we get our last position instead */ + ptr = *end; + + /* pass all letters that are including in the separator string */ + while(*ptr && strchr(sep, *ptr)) + ++ptr; + + if(*ptr) { + /* so this is where the next piece of string starts */ + char *start = ptr; + + /* set the end pointer to the first byte after the start */ + *end = start + 1; + + /* scan through the string to find where it ends, it ends on a + null byte or a character that exists in the separator string */ + while(**end && !strchr(sep, **end)) + ++*end; + + if(**end) { + /* the end is not a null byte */ + **end = '\0'; /* null-terminate it! */ + ++*end; /* advance the last pointer to beyond the null byte */ + } + + return start; /* return the position where the string starts */ + } + + /* we ended up on a null byte, there are no more strings to find! */ + return NULL; +} + +#endif /* this was only compiled if strtok_r wasn't present */ diff --git a/Utilities/cmcurl/lib/strtok.h b/Utilities/cmcurl/lib/strtok.h new file mode 100644 index 0000000..321cba2 --- /dev/null +++ b/Utilities/cmcurl/lib/strtok.h @@ -0,0 +1,36 @@ +#ifndef HEADER_CURL_STRTOK_H +#define HEADER_CURL_STRTOK_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" +#include <stddef.h> + +#ifndef HAVE_STRTOK_R +char *Curl_strtok_r(char *s, const char *delim, char **last); +#define strtok_r Curl_strtok_r +#else +#include <string.h> +#endif + +#endif /* HEADER_CURL_STRTOK_H */ diff --git a/Utilities/cmcurl/lib/strtoofft.c b/Utilities/cmcurl/lib/strtoofft.c new file mode 100644 index 0000000..077b257 --- /dev/null +++ b/Utilities/cmcurl/lib/strtoofft.c @@ -0,0 +1,245 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include <errno.h> +#include "curl_setup.h" + +#include "strtoofft.h" + +/* + * NOTE: + * + * In the ISO C standard (IEEE Std 1003.1), there is a strtoimax() function we + * could use in case strtoll() doesn't exist... See + * https://www.opengroup.org/onlinepubs/009695399/functions/strtoimax.html + */ + +#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) +# ifdef HAVE_STRTOLL +# define strtooff strtoll +# else +# if defined(_MSC_VER) && (_MSC_VER >= 1300) && (_INTEGRAL_MAX_BITS >= 64) +# if defined(_SAL_VERSION) + _Check_return_ _CRTIMP __int64 __cdecl _strtoi64( + _In_z_ const char *_String, + _Out_opt_ _Deref_post_z_ char **_EndPtr, _In_ int _Radix); +# else + _CRTIMP __int64 __cdecl _strtoi64(const char *_String, + char **_EndPtr, int _Radix); +# endif +# define strtooff _strtoi64 +# else +# define PRIVATE_STRTOOFF 1 +# endif +# endif +#else +# define strtooff strtol +#endif + +#ifdef PRIVATE_STRTOOFF + +/* Range tests can be used for alphanum decoding if characters are consecutive, + like in ASCII. Else an array is scanned. Determine this condition now. */ + +#if('9' - '0') != 9 || ('Z' - 'A') != 25 || ('z' - 'a') != 25 + +#define NO_RANGE_TEST + +static const char valchars[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; +#endif + +static int get_char(char c, int base); + +/** + * Custom version of the strtooff function. This extracts a curl_off_t + * value from the given input string and returns it. + */ +static curl_off_t strtooff(const char *nptr, char **endptr, int base) +{ + char *end; + int is_negative = 0; + int overflow; + int i; + curl_off_t value = 0; + curl_off_t newval; + + /* Skip leading whitespace. */ + end = (char *)nptr; + while(ISBLANK(end[0])) { + end++; + } + + /* Handle the sign, if any. */ + if(end[0] == '-') { + is_negative = 1; + end++; + } + else if(end[0] == '+') { + end++; + } + else if(end[0] == '\0') { + /* We had nothing but perhaps some whitespace -- there was no number. */ + if(endptr) { + *endptr = end; + } + return 0; + } + + /* Handle special beginnings, if present and allowed. */ + if(end[0] == '0' && end[1] == 'x') { + if(base == 16 || base == 0) { + end += 2; + base = 16; + } + } + else if(end[0] == '0') { + if(base == 8 || base == 0) { + end++; + base = 8; + } + } + + /* Matching strtol, if the base is 0 and it doesn't look like + * the number is octal or hex, we assume it's base 10. + */ + if(base == 0) { + base = 10; + } + + /* Loop handling digits. */ + value = 0; + overflow = 0; + for(i = get_char(end[0], base); + i != -1; + end++, i = get_char(end[0], base)) { + newval = base * value + i; + if(newval < value) { + /* We've overflowed. */ + overflow = 1; + break; + } + else + value = newval; + } + + if(!overflow) { + if(is_negative) { + /* Fix the sign. */ + value *= -1; + } + } + else { + if(is_negative) + value = CURL_OFF_T_MIN; + else + value = CURL_OFF_T_MAX; + + errno = ERANGE; + } + + if(endptr) + *endptr = end; + + return value; +} + +/** + * Returns the value of c in the given base, or -1 if c cannot + * be interpreted properly in that base (i.e., is out of range, + * is a null, etc.). + * + * @param c the character to interpret according to base + * @param base the base in which to interpret c + * + * @return the value of c in base, or -1 if c isn't in range + */ +static int get_char(char c, int base) +{ +#ifndef NO_RANGE_TEST + int value = -1; + if(c <= '9' && c >= '0') { + value = c - '0'; + } + else if(c <= 'Z' && c >= 'A') { + value = c - 'A' + 10; + } + else if(c <= 'z' && c >= 'a') { + value = c - 'a' + 10; + } +#else + const char *cp; + int value; + + cp = memchr(valchars, c, 10 + 26 + 26); + + if(!cp) + return -1; + + value = cp - valchars; + + if(value >= 10 + 26) + value -= 26; /* Lowercase. */ +#endif + + if(value >= base) { + value = -1; + } + + return value; +} +#endif /* Only present if we need strtoll, but don't have it. */ + +/* + * Parse a *positive* up to 64 bit number written in ascii. + */ +CURLofft curlx_strtoofft(const char *str, char **endp, int base, + curl_off_t *num) +{ + char *end; + curl_off_t number; + errno = 0; + *num = 0; /* clear by default */ + DEBUGASSERT(base); /* starting now, avoid base zero */ + + while(*str && ISBLANK(*str)) + str++; + if(('-' == *str) || (ISSPACE(*str))) { + if(endp) + *endp = (char *)str; /* didn't actually move */ + return CURL_OFFT_INVAL; /* nothing parsed */ + } + number = strtooff(str, &end, base); + if(endp) + *endp = end; + if(errno == ERANGE) + /* overflow/underflow */ + return CURL_OFFT_FLOW; + else if(str == end) + /* nothing parsed */ + return CURL_OFFT_INVAL; + + *num = number; + return CURL_OFFT_OK; +} diff --git a/Utilities/cmcurl/lib/strtoofft.h b/Utilities/cmcurl/lib/strtoofft.h new file mode 100644 index 0000000..34d293b --- /dev/null +++ b/Utilities/cmcurl/lib/strtoofft.h @@ -0,0 +1,54 @@ +#ifndef HEADER_CURL_STRTOOFFT_H +#define HEADER_CURL_STRTOOFFT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +/* + * Determine which string to integral data type conversion function we use + * to implement string conversion to our curl_off_t integral data type. + * + * Notice that curl_off_t might be 64 or 32 bit wide, and that it might use + * an underlying data type which might be 'long', 'int64_t', 'long long' or + * '__int64' and more remotely other data types. + * + * On systems where the size of curl_off_t is greater than the size of 'long' + * the conversion function to use is strtoll() if it is available, otherwise, + * we emulate its functionality with our own clone. + * + * On systems where the size of curl_off_t is smaller or equal than the size + * of 'long' the conversion function to use is strtol(). + */ + +typedef enum { + CURL_OFFT_OK, /* parsed fine */ + CURL_OFFT_FLOW, /* over or underflow */ + CURL_OFFT_INVAL /* nothing was parsed */ +} CURLofft; + +CURLofft curlx_strtoofft(const char *str, char **endp, int base, + curl_off_t *num); + +#endif /* HEADER_CURL_STRTOOFFT_H */ diff --git a/Utilities/cmcurl/lib/system_win32.c b/Utilities/cmcurl/lib/system_win32.c new file mode 100644 index 0000000..9408d02 --- /dev/null +++ b/Utilities/cmcurl/lib/system_win32.c @@ -0,0 +1,241 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * + * 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(_WIN32) + +#include <curl/curl.h> +#include "system_win32.h" +#include "version_win32.h" +#include "curl_sspi.h" +#include "warnless.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +LARGE_INTEGER Curl_freq; +bool Curl_isVistaOrGreater; + +/* Handle of iphlpapp.dll */ +static HMODULE s_hIpHlpApiDll = NULL; + +/* Pointer to the if_nametoindex function */ +IF_NAMETOINDEX_FN Curl_if_nametoindex = NULL; + +/* Curl_win32_init() performs win32 global initialization */ +CURLcode Curl_win32_init(long flags) +{ + /* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which + is just for Winsock at the moment. Any required win32 initialization + 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 couldn't find a usable */ + /* winsock.dll. */ + 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 couldn't find a usable */ + + /* winsock.dll. */ + WSACleanup(); + return CURLE_FAILED_INIT; + } + /* The Windows Sockets DLL is acceptable. Proceed. */ +#elif defined(USE_LWIPSOCK) + lwip_init(); +#endif + } /* CURL_GLOBAL_WIN32 */ + +#ifdef USE_WINDOWS_SSPI + { + CURLcode result = Curl_sspi_global_init(); + if(result) + return result; + } +#endif + + s_hIpHlpApiDll = Curl_load_library(TEXT("iphlpapi.dll")); + if(s_hIpHlpApiDll) { + /* Get the address of the if_nametoindex function */ + IF_NAMETOINDEX_FN pIfNameToIndex = + CURLX_FUNCTION_CAST(IF_NAMETOINDEX_FN, + (GetProcAddress(s_hIpHlpApiDll, "if_nametoindex"))); + + if(pIfNameToIndex) + Curl_if_nametoindex = pIfNameToIndex; + } + + /* curlx_verify_windows_version must be called during init at least once + because it has its own initialization routine. */ + if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + Curl_isVistaOrGreater = TRUE; + } + else + Curl_isVistaOrGreater = FALSE; + + QueryPerformanceFrequency(&Curl_freq); + return CURLE_OK; +} + +/* Curl_win32_cleanup() is the opposite of Curl_win32_init() */ +void Curl_win32_cleanup(long init_flags) +{ + if(s_hIpHlpApiDll) { + FreeLibrary(s_hIpHlpApiDll); + s_hIpHlpApiDll = NULL; + Curl_if_nametoindex = NULL; + } + +#ifdef USE_WINDOWS_SSPI + Curl_sspi_global_cleanup(); +#endif + + if(init_flags & CURL_GLOBAL_WIN32) { +#ifdef USE_WINSOCK + WSACleanup(); +#endif + } +} + +#if !defined(LOAD_WITH_ALTERED_SEARCH_PATH) +#define LOAD_WITH_ALTERED_SEARCH_PATH 0x00000008 +#endif + +#if !defined(LOAD_LIBRARY_SEARCH_SYSTEM32) +#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 +#endif + +/* We use our own typedef here since some headers might lack these */ +typedef HMODULE (APIENTRY *LOADLIBRARYEX_FN)(LPCTSTR, HANDLE, DWORD); + +/* See function definitions in winbase.h */ +#ifdef UNICODE +# ifdef _WIN32_WCE +# define LOADLIBARYEX L"LoadLibraryExW" +# else +# define LOADLIBARYEX "LoadLibraryExW" +# endif +#else +# define LOADLIBARYEX "LoadLibraryExA" +#endif + +/* + * Curl_load_library() + * + * This is used to dynamically load DLLs using the most secure method available + * for the version of Windows that we are running on. + * + * Parameters: + * + * filename [in] - The filename or full path of the DLL to load. If only the + * filename is passed then the DLL will be loaded from the + * Windows system directory. + * + * Returns the handle of the module on success; otherwise NULL. + */ +HMODULE Curl_load_library(LPCTSTR filename) +{ +#ifndef CURL_WINDOWS_APP + HMODULE hModule = NULL; + LOADLIBRARYEX_FN pLoadLibraryEx = NULL; + + /* Get a handle to kernel32 so we can access it's functions at runtime */ + HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32")); + if(!hKernel32) + return NULL; + + /* Attempt to find LoadLibraryEx() which is only available on Windows 2000 + and above */ + pLoadLibraryEx = + CURLX_FUNCTION_CAST(LOADLIBRARYEX_FN, + (GetProcAddress(hKernel32, LOADLIBARYEX))); + + /* Detect if there's already a path in the filename and load the library if + there is. Note: Both back slashes and forward slashes have been supported + since the earlier days of DOS at an API level although they are not + supported by command prompt */ + if(_tcspbrk(filename, TEXT("\\/"))) { + /** !checksrc! disable BANNEDFUNC 1 **/ + hModule = pLoadLibraryEx ? + pLoadLibraryEx(filename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH) : + LoadLibrary(filename); + } + /* Detect if KB2533623 is installed, as LOAD_LIBRARY_SEARCH_SYSTEM32 is only + supported on Windows Vista, Windows Server 2008, Windows 7 and Windows + Server 2008 R2 with this patch or natively on Windows 8 and above */ + else if(pLoadLibraryEx && GetProcAddress(hKernel32, "AddDllDirectory")) { + /* Load the DLL from the Windows system directory */ + hModule = pLoadLibraryEx(filename, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + } + else { + /* Attempt to get the Windows system path */ + UINT systemdirlen = GetSystemDirectory(NULL, 0); + if(systemdirlen) { + /* Allocate space for the full DLL path (Room for the null terminator + is included in systemdirlen) */ + size_t filenamelen = _tcslen(filename); + TCHAR *path = malloc(sizeof(TCHAR) * (systemdirlen + 1 + filenamelen)); + if(path && GetSystemDirectory(path, systemdirlen)) { + /* Calculate the full DLL path */ + _tcscpy(path + _tcslen(path), TEXT("\\")); + _tcscpy(path + _tcslen(path), filename); + + /* Load the DLL from the Windows system directory */ + /** !checksrc! disable BANNEDFUNC 1 **/ + hModule = pLoadLibraryEx ? + pLoadLibraryEx(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH) : + LoadLibrary(path); + + } + free(path); + } + } + return hModule; +#else + /* the Universal Windows Platform (UWP) can't do this */ + (void)filename; + return NULL; +#endif +} + +#endif /* _WIN32 */ diff --git a/Utilities/cmcurl/lib/system_win32.h b/Utilities/cmcurl/lib/system_win32.h new file mode 100644 index 0000000..2566766 --- /dev/null +++ b/Utilities/cmcurl/lib/system_win32.h @@ -0,0 +1,49 @@ +#ifndef HEADER_CURL_SYSTEM_WIN32_H +#define HEADER_CURL_SYSTEM_WIN32_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * + * 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(_WIN32) + +extern LARGE_INTEGER Curl_freq; +extern bool Curl_isVistaOrGreater; + +CURLcode Curl_win32_init(long flags); +void Curl_win32_cleanup(long init_flags); + +/* We use our own typedef here since some headers might lack this */ +typedef unsigned int(WINAPI *IF_NAMETOINDEX_FN)(const char *); + +/* This is used instead of if_nametoindex if available on Windows */ +extern IF_NAMETOINDEX_FN Curl_if_nametoindex; + +/* This is used to dynamically load DLLs */ +HMODULE Curl_load_library(LPCTSTR filename); +#else /* _WIN32 */ +#define Curl_win32_init(x) CURLE_OK +#endif /* !_WIN32 */ + +#endif /* HEADER_CURL_SYSTEM_WIN32_H */ diff --git a/Utilities/cmcurl/lib/telnet.c b/Utilities/cmcurl/lib/telnet.c new file mode 100644 index 0000000..836e255 --- /dev/null +++ b/Utilities/cmcurl/lib/telnet.c @@ -0,0 +1,1643 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_TELNET + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#include "urldata.h" +#include <curl/curl.h> +#include "transfer.h" +#include "sendf.h" +#include "telnet.h" +#include "connect.h" +#include "progress.h" +#include "system_win32.h" +#include "arpa_telnet.h" +#include "select.h" +#include "strcase.h" +#include "warnless.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define SUBBUFSIZE 512 + +#define CURL_SB_CLEAR(x) x->subpointer = x->subbuffer +#define CURL_SB_TERM(x) \ + do { \ + x->subend = x->subpointer; \ + CURL_SB_CLEAR(x); \ + } while(0) +#define CURL_SB_ACCUM(x,c) \ + do { \ + if(x->subpointer < (x->subbuffer + sizeof(x->subbuffer))) \ + *x->subpointer++ = (c); \ + } while(0) + +#define CURL_SB_GET(x) ((*x->subpointer++)&0xff) +#define CURL_SB_LEN(x) (x->subend - x->subpointer) + +/* For posterity: +#define CURL_SB_PEEK(x) ((*x->subpointer)&0xff) +#define CURL_SB_EOF(x) (x->subpointer >= x->subend) */ + +#ifdef CURL_DISABLE_VERBOSE_STRINGS +#define printoption(a,b,c,d) Curl_nop_stmt +#endif + +static +CURLcode telrcv(struct Curl_easy *data, + const unsigned char *inbuf, /* Data received from socket */ + ssize_t count); /* Number of bytes received */ + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void printoption(struct Curl_easy *data, + const char *direction, + int cmd, int option); +#endif + +static void negotiate(struct Curl_easy *data); +static void send_negotiation(struct Curl_easy *data, int cmd, int option); +static void set_local_option(struct Curl_easy *data, + int option, int newstate); +static void set_remote_option(struct Curl_easy *data, + int option, int newstate); + +static void printsub(struct Curl_easy *data, + int direction, unsigned char *pointer, + size_t length); +static void suboption(struct Curl_easy *data); +static void sendsuboption(struct Curl_easy *data, int option); + +static CURLcode telnet_do(struct Curl_easy *data, bool *done); +static CURLcode telnet_done(struct Curl_easy *data, + CURLcode, bool premature); +static CURLcode send_telnet_data(struct Curl_easy *data, + char *buffer, ssize_t nread); + +/* For negotiation compliant to RFC 1143 */ +#define CURL_NO 0 +#define CURL_YES 1 +#define CURL_WANTYES 2 +#define CURL_WANTNO 3 + +#define CURL_EMPTY 0 +#define CURL_OPPOSITE 1 + +/* + * Telnet receiver states for fsm + */ +typedef enum +{ + CURL_TS_DATA = 0, + CURL_TS_IAC, + CURL_TS_WILL, + CURL_TS_WONT, + CURL_TS_DO, + CURL_TS_DONT, + CURL_TS_CR, + CURL_TS_SB, /* sub-option collection */ + CURL_TS_SE /* looking for sub-option end */ +} TelnetReceive; + +struct TELNET { + int please_negotiate; + int already_negotiated; + int us[256]; + int usq[256]; + int us_preferred[256]; + int him[256]; + int himq[256]; + int him_preferred[256]; + int subnegotiation[256]; + char subopt_ttype[32]; /* Set with suboption TTYPE */ + char subopt_xdisploc[128]; /* Set with suboption XDISPLOC */ + unsigned short subopt_wsx; /* Set with suboption NAWS */ + unsigned short subopt_wsy; /* Set with suboption NAWS */ + TelnetReceive telrcv_state; + struct curl_slist *telnet_vars; /* Environment variables */ + + /* suboptions */ + unsigned char subbuffer[SUBBUFSIZE]; + unsigned char *subpointer, *subend; /* buffer for sub-options */ +}; + + +/* + * TELNET protocol handler. + */ + +const struct Curl_handler Curl_handler_telnet = { + "TELNET", /* scheme */ + ZERO_NULL, /* setup_connection */ + telnet_do, /* do_it */ + telnet_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_TELNET, /* defport */ + CURLPROTO_TELNET, /* protocol */ + CURLPROTO_TELNET, /* family */ + PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ +}; + + +static +CURLcode init_telnet(struct Curl_easy *data) +{ + struct TELNET *tn; + + tn = calloc(1, sizeof(struct TELNET)); + if(!tn) + return CURLE_OUT_OF_MEMORY; + + data->req.p.telnet = tn; /* make us known */ + + tn->telrcv_state = CURL_TS_DATA; + + /* Init suboptions */ + CURL_SB_CLEAR(tn); + + /* Set the options we want by default */ + tn->us_preferred[CURL_TELOPT_SGA] = CURL_YES; + tn->him_preferred[CURL_TELOPT_SGA] = CURL_YES; + + /* To be compliant with previous releases of libcurl + we enable this option by default. This behavior + can be changed thanks to the "BINARY" option in + CURLOPT_TELNETOPTIONS + */ + tn->us_preferred[CURL_TELOPT_BINARY] = CURL_YES; + tn->him_preferred[CURL_TELOPT_BINARY] = CURL_YES; + + /* We must allow the server to echo what we sent + but it is not necessary to request the server + to do so (it might forces the server to close + the connection). Hence, we ignore ECHO in the + negotiate function + */ + tn->him_preferred[CURL_TELOPT_ECHO] = CURL_YES; + + /* Set the subnegotiation fields to send information + just after negotiation passed (do/will) + + Default values are (0,0) initialized by calloc. + According to the RFC1013 it is valid: + A value equal to zero is acceptable for the width (or height), + and means that no character width (or height) is being sent. + In this case, the width (or height) that will be assumed by the + Telnet server is operating system specific (it will probably be + based upon the terminal type information that may have been sent + using the TERMINAL TYPE Telnet option). */ + tn->subnegotiation[CURL_TELOPT_NAWS] = CURL_YES; + return CURLE_OK; +} + +static void negotiate(struct Curl_easy *data) +{ + int i; + struct TELNET *tn = data->req.p.telnet; + + for(i = 0; i < CURL_NTELOPTS; i++) { + if(i == CURL_TELOPT_ECHO) + continue; + + if(tn->us_preferred[i] == CURL_YES) + set_local_option(data, i, CURL_YES); + + if(tn->him_preferred[i] == CURL_YES) + set_remote_option(data, i, CURL_YES); + } +} + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void printoption(struct Curl_easy *data, + const char *direction, int cmd, int option) +{ + if(data->set.verbose) { + if(cmd == CURL_IAC) { + if(CURL_TELCMD_OK(option)) + infof(data, "%s IAC %s", direction, CURL_TELCMD(option)); + else + infof(data, "%s IAC %d", direction, option); + } + else { + const char *fmt = (cmd == CURL_WILL) ? "WILL" : + (cmd == CURL_WONT) ? "WONT" : + (cmd == CURL_DO) ? "DO" : + (cmd == CURL_DONT) ? "DONT" : 0; + if(fmt) { + const char *opt; + if(CURL_TELOPT_OK(option)) + opt = CURL_TELOPT(option); + else if(option == CURL_TELOPT_EXOPL) + opt = "EXOPL"; + else + opt = NULL; + + if(opt) + infof(data, "%s %s %s", direction, fmt, opt); + else + infof(data, "%s %s %d", direction, fmt, option); + } + else + infof(data, "%s %d %d", direction, cmd, option); + } + } +} +#endif + +static void send_negotiation(struct Curl_easy *data, int cmd, int option) +{ + unsigned char buf[3]; + ssize_t bytes_written; + struct connectdata *conn = data->conn; + + buf[0] = CURL_IAC; + buf[1] = (unsigned char)cmd; + buf[2] = (unsigned char)option; + + bytes_written = swrite(conn->sock[FIRSTSOCKET], buf, 3); + if(bytes_written < 0) { + int err = SOCKERRNO; + failf(data,"Sending data failed (%d)",err); + } + + printoption(data, "SENT", cmd, option); +} + +static +void set_remote_option(struct Curl_easy *data, int option, int newstate) +{ + struct TELNET *tn = data->req.p.telnet; + if(newstate == CURL_YES) { + switch(tn->him[option]) { + case CURL_NO: + tn->him[option] = CURL_WANTYES; + send_negotiation(data, CURL_DO, option); + break; + + case CURL_YES: + /* Already enabled */ + break; + + case CURL_WANTNO: + switch(tn->himq[option]) { + case CURL_EMPTY: + /* Already negotiating for CURL_YES, queue the request */ + tn->himq[option] = CURL_OPPOSITE; + break; + case CURL_OPPOSITE: + /* Error: already queued an enable request */ + break; + } + break; + + case CURL_WANTYES: + switch(tn->himq[option]) { + case CURL_EMPTY: + /* Error: already negotiating for enable */ + break; + case CURL_OPPOSITE: + tn->himq[option] = CURL_EMPTY; + break; + } + break; + } + } + else { /* NO */ + switch(tn->him[option]) { + case CURL_NO: + /* Already disabled */ + break; + + case CURL_YES: + tn->him[option] = CURL_WANTNO; + send_negotiation(data, CURL_DONT, option); + break; + + case CURL_WANTNO: + switch(tn->himq[option]) { + case CURL_EMPTY: + /* Already negotiating for NO */ + break; + case CURL_OPPOSITE: + tn->himq[option] = CURL_EMPTY; + break; + } + break; + + case CURL_WANTYES: + switch(tn->himq[option]) { + case CURL_EMPTY: + tn->himq[option] = CURL_OPPOSITE; + break; + case CURL_OPPOSITE: + break; + } + break; + } + } +} + +static +void rec_will(struct Curl_easy *data, int option) +{ + struct TELNET *tn = data->req.p.telnet; + switch(tn->him[option]) { + case CURL_NO: + if(tn->him_preferred[option] == CURL_YES) { + tn->him[option] = CURL_YES; + send_negotiation(data, CURL_DO, option); + } + else + send_negotiation(data, CURL_DONT, option); + + break; + + case CURL_YES: + /* Already enabled */ + break; + + case CURL_WANTNO: + switch(tn->himq[option]) { + case CURL_EMPTY: + /* Error: DONT answered by WILL */ + tn->him[option] = CURL_NO; + break; + case CURL_OPPOSITE: + /* Error: DONT answered by WILL */ + tn->him[option] = CURL_YES; + tn->himq[option] = CURL_EMPTY; + break; + } + break; + + case CURL_WANTYES: + switch(tn->himq[option]) { + case CURL_EMPTY: + tn->him[option] = CURL_YES; + break; + case CURL_OPPOSITE: + tn->him[option] = CURL_WANTNO; + tn->himq[option] = CURL_EMPTY; + send_negotiation(data, CURL_DONT, option); + break; + } + break; + } +} + +static +void rec_wont(struct Curl_easy *data, int option) +{ + struct TELNET *tn = data->req.p.telnet; + switch(tn->him[option]) { + case CURL_NO: + /* Already disabled */ + break; + + case CURL_YES: + tn->him[option] = CURL_NO; + send_negotiation(data, CURL_DONT, option); + break; + + case CURL_WANTNO: + switch(tn->himq[option]) { + case CURL_EMPTY: + tn->him[option] = CURL_NO; + break; + + case CURL_OPPOSITE: + tn->him[option] = CURL_WANTYES; + tn->himq[option] = CURL_EMPTY; + send_negotiation(data, CURL_DO, option); + break; + } + break; + + case CURL_WANTYES: + switch(tn->himq[option]) { + case CURL_EMPTY: + tn->him[option] = CURL_NO; + break; + case CURL_OPPOSITE: + tn->him[option] = CURL_NO; + tn->himq[option] = CURL_EMPTY; + break; + } + break; + } +} + +static void +set_local_option(struct Curl_easy *data, int option, int newstate) +{ + struct TELNET *tn = data->req.p.telnet; + if(newstate == CURL_YES) { + switch(tn->us[option]) { + case CURL_NO: + tn->us[option] = CURL_WANTYES; + send_negotiation(data, CURL_WILL, option); + break; + + case CURL_YES: + /* Already enabled */ + break; + + case CURL_WANTNO: + switch(tn->usq[option]) { + case CURL_EMPTY: + /* Already negotiating for CURL_YES, queue the request */ + tn->usq[option] = CURL_OPPOSITE; + break; + case CURL_OPPOSITE: + /* Error: already queued an enable request */ + break; + } + break; + + case CURL_WANTYES: + switch(tn->usq[option]) { + case CURL_EMPTY: + /* Error: already negotiating for enable */ + break; + case CURL_OPPOSITE: + tn->usq[option] = CURL_EMPTY; + break; + } + break; + } + } + else { /* NO */ + switch(tn->us[option]) { + case CURL_NO: + /* Already disabled */ + break; + + case CURL_YES: + tn->us[option] = CURL_WANTNO; + send_negotiation(data, CURL_WONT, option); + break; + + case CURL_WANTNO: + switch(tn->usq[option]) { + case CURL_EMPTY: + /* Already negotiating for NO */ + break; + case CURL_OPPOSITE: + tn->usq[option] = CURL_EMPTY; + break; + } + break; + + case CURL_WANTYES: + switch(tn->usq[option]) { + case CURL_EMPTY: + tn->usq[option] = CURL_OPPOSITE; + break; + case CURL_OPPOSITE: + break; + } + break; + } + } +} + +static +void rec_do(struct Curl_easy *data, int option) +{ + struct TELNET *tn = data->req.p.telnet; + switch(tn->us[option]) { + case CURL_NO: + if(tn->us_preferred[option] == CURL_YES) { + tn->us[option] = CURL_YES; + send_negotiation(data, CURL_WILL, option); + if(tn->subnegotiation[option] == CURL_YES) + /* transmission of data option */ + sendsuboption(data, option); + } + else if(tn->subnegotiation[option] == CURL_YES) { + /* send information to achieve this option */ + tn->us[option] = CURL_YES; + send_negotiation(data, CURL_WILL, option); + sendsuboption(data, option); + } + else + send_negotiation(data, CURL_WONT, option); + break; + + case CURL_YES: + /* Already enabled */ + break; + + case CURL_WANTNO: + switch(tn->usq[option]) { + case CURL_EMPTY: + /* Error: DONT answered by WILL */ + tn->us[option] = CURL_NO; + break; + case CURL_OPPOSITE: + /* Error: DONT answered by WILL */ + tn->us[option] = CURL_YES; + tn->usq[option] = CURL_EMPTY; + break; + } + break; + + case CURL_WANTYES: + switch(tn->usq[option]) { + case CURL_EMPTY: + tn->us[option] = CURL_YES; + if(tn->subnegotiation[option] == CURL_YES) { + /* transmission of data option */ + sendsuboption(data, option); + } + break; + case CURL_OPPOSITE: + tn->us[option] = CURL_WANTNO; + tn->himq[option] = CURL_EMPTY; + send_negotiation(data, CURL_WONT, option); + break; + } + break; + } +} + +static +void rec_dont(struct Curl_easy *data, int option) +{ + struct TELNET *tn = data->req.p.telnet; + switch(tn->us[option]) { + case CURL_NO: + /* Already disabled */ + break; + + case CURL_YES: + tn->us[option] = CURL_NO; + send_negotiation(data, CURL_WONT, option); + break; + + case CURL_WANTNO: + switch(tn->usq[option]) { + case CURL_EMPTY: + tn->us[option] = CURL_NO; + break; + + case CURL_OPPOSITE: + tn->us[option] = CURL_WANTYES; + tn->usq[option] = CURL_EMPTY; + send_negotiation(data, CURL_WILL, option); + break; + } + break; + + case CURL_WANTYES: + switch(tn->usq[option]) { + case CURL_EMPTY: + tn->us[option] = CURL_NO; + break; + case CURL_OPPOSITE: + tn->us[option] = CURL_NO; + tn->usq[option] = CURL_EMPTY; + break; + } + break; + } +} + + +static void printsub(struct Curl_easy *data, + int direction, /* '<' or '>' */ + unsigned char *pointer, /* where suboption data is */ + size_t length) /* length of suboption data */ +{ + if(data->set.verbose) { + unsigned int i = 0; + if(direction) { + infof(data, "%s IAC SB ", (direction == '<')? "RCVD":"SENT"); + if(length >= 3) { + int j; + + i = pointer[length-2]; + j = pointer[length-1]; + + if(i != CURL_IAC || j != CURL_SE) { + infof(data, "(terminated by "); + if(CURL_TELOPT_OK(i)) + infof(data, "%s ", CURL_TELOPT(i)); + else if(CURL_TELCMD_OK(i)) + infof(data, "%s ", CURL_TELCMD(i)); + else + infof(data, "%u ", i); + if(CURL_TELOPT_OK(j)) + infof(data, "%s", CURL_TELOPT(j)); + else if(CURL_TELCMD_OK(j)) + infof(data, "%s", CURL_TELCMD(j)); + else + infof(data, "%d", j); + infof(data, ", not IAC SE) "); + } + } + length -= 2; + } + if(length < 1) { + infof(data, "(Empty suboption?)"); + return; + } + + if(CURL_TELOPT_OK(pointer[0])) { + switch(pointer[0]) { + case CURL_TELOPT_TTYPE: + case CURL_TELOPT_XDISPLOC: + case CURL_TELOPT_NEW_ENVIRON: + case CURL_TELOPT_NAWS: + infof(data, "%s", CURL_TELOPT(pointer[0])); + break; + default: + infof(data, "%s (unsupported)", CURL_TELOPT(pointer[0])); + break; + } + } + else + infof(data, "%d (unknown)", pointer[i]); + + switch(pointer[0]) { + case CURL_TELOPT_NAWS: + if(length > 4) + infof(data, "Width: %d ; Height: %d", (pointer[1]<<8) | pointer[2], + (pointer[3]<<8) | pointer[4]); + break; + default: + switch(pointer[1]) { + case CURL_TELQUAL_IS: + infof(data, " IS"); + break; + case CURL_TELQUAL_SEND: + infof(data, " SEND"); + break; + case CURL_TELQUAL_INFO: + infof(data, " INFO/REPLY"); + break; + case CURL_TELQUAL_NAME: + infof(data, " NAME"); + break; + } + + switch(pointer[0]) { + case CURL_TELOPT_TTYPE: + case CURL_TELOPT_XDISPLOC: + pointer[length] = 0; + infof(data, " \"%s\"", &pointer[2]); + break; + case CURL_TELOPT_NEW_ENVIRON: + if(pointer[1] == CURL_TELQUAL_IS) { + infof(data, " "); + for(i = 3; i < length; i++) { + switch(pointer[i]) { + case CURL_NEW_ENV_VAR: + infof(data, ", "); + break; + case CURL_NEW_ENV_VALUE: + infof(data, " = "); + break; + default: + infof(data, "%c", pointer[i]); + break; + } + } + } + break; + default: + for(i = 2; i < length; i++) + infof(data, " %.2x", pointer[i]); + break; + } + } + } +} + +#ifdef _MSC_VER +#pragma warning(push) +/* warning C4706: assignment within conditional expression */ +#pragma warning(disable:4706) +#endif +static bool str_is_nonascii(const char *str) +{ + char c; + while((c = *str++)) + if(c & 0x80) + return TRUE; + + return FALSE; +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +static CURLcode check_telnet_options(struct Curl_easy *data) +{ + struct curl_slist *head; + struct curl_slist *beg; + struct TELNET *tn = data->req.p.telnet; + CURLcode result = CURLE_OK; + + /* Add the user name as an environment variable if it + was given on the command line */ + if(data->state.aptr.user) { + char buffer[256]; + if(str_is_nonascii(data->conn->user)) + return CURLE_BAD_FUNCTION_ARGUMENT; + msnprintf(buffer, sizeof(buffer), "USER,%s", data->conn->user); + beg = curl_slist_append(tn->telnet_vars, buffer); + if(!beg) { + curl_slist_free_all(tn->telnet_vars); + tn->telnet_vars = NULL; + return CURLE_OUT_OF_MEMORY; + } + tn->telnet_vars = beg; + tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES; + } + + for(head = data->set.telnet_options; head && !result; head = head->next) { + size_t olen; + char *option = head->data; + char *arg; + char *sep = strchr(option, '='); + if(sep) { + olen = sep - option; + arg = ++sep; + if(str_is_nonascii(arg)) + continue; + switch(olen) { + case 5: + /* Terminal type */ + if(strncasecompare(option, "TTYPE", 5)) { + strncpy(tn->subopt_ttype, arg, 31); + tn->subopt_ttype[31] = 0; /* String termination */ + tn->us_preferred[CURL_TELOPT_TTYPE] = CURL_YES; + } + else + result = CURLE_UNKNOWN_OPTION; + break; + + case 8: + /* Display variable */ + if(strncasecompare(option, "XDISPLOC", 8)) { + strncpy(tn->subopt_xdisploc, arg, 127); + tn->subopt_xdisploc[127] = 0; /* String termination */ + tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES; + } + else + result = CURLE_UNKNOWN_OPTION; + break; + + case 7: + /* Environment variable */ + if(strncasecompare(option, "NEW_ENV", 7)) { + beg = curl_slist_append(tn->telnet_vars, arg); + if(!beg) { + result = CURLE_OUT_OF_MEMORY; + break; + } + tn->telnet_vars = beg; + tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES; + } + else + result = CURLE_UNKNOWN_OPTION; + break; + + case 2: + /* Window Size */ + if(strncasecompare(option, "WS", 2)) { + char *p; + unsigned long x = strtoul(arg, &p, 10); + unsigned long y = 0; + if(x && (x <= 0xffff) && Curl_raw_tolower(*p) == 'x') { + p++; + y = strtoul(p, NULL, 10); + if(y && (y <= 0xffff)) { + tn->subopt_wsx = (unsigned short)x; + tn->subopt_wsy = (unsigned short)y; + tn->us_preferred[CURL_TELOPT_NAWS] = CURL_YES; + } + } + if(!y) { + failf(data, "Syntax error in telnet option: %s", head->data); + result = CURLE_SETOPT_OPTION_SYNTAX; + } + } + else + result = CURLE_UNKNOWN_OPTION; + break; + + case 6: + /* To take care or not of the 8th bit in data exchange */ + if(strncasecompare(option, "BINARY", 6)) { + int binary_option = atoi(arg); + if(binary_option != 1) { + tn->us_preferred[CURL_TELOPT_BINARY] = CURL_NO; + tn->him_preferred[CURL_TELOPT_BINARY] = CURL_NO; + } + } + else + result = CURLE_UNKNOWN_OPTION; + break; + default: + failf(data, "Unknown telnet option %s", head->data); + result = CURLE_UNKNOWN_OPTION; + break; + } + } + else { + failf(data, "Syntax error in telnet option: %s", head->data); + result = CURLE_SETOPT_OPTION_SYNTAX; + } + } + + if(result) { + curl_slist_free_all(tn->telnet_vars); + tn->telnet_vars = NULL; + } + + return result; +} + +/* + * suboption() + * + * Look at the sub-option buffer, and try to be helpful to the other + * side. + */ + +static void suboption(struct Curl_easy *data) +{ + struct curl_slist *v; + unsigned char temp[2048]; + ssize_t bytes_written; + size_t len; + int err; + struct TELNET *tn = data->req.p.telnet; + struct connectdata *conn = data->conn; + + printsub(data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN(tn) + 2); + switch(CURL_SB_GET(tn)) { + case CURL_TELOPT_TTYPE: + len = strlen(tn->subopt_ttype) + 4 + 2; + msnprintf((char *)temp, sizeof(temp), + "%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_TTYPE, + CURL_TELQUAL_IS, tn->subopt_ttype, CURL_IAC, CURL_SE); + bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); + if(bytes_written < 0) { + err = SOCKERRNO; + failf(data,"Sending data failed (%d)",err); + } + printsub(data, '>', &temp[2], len-2); + break; + case CURL_TELOPT_XDISPLOC: + len = strlen(tn->subopt_xdisploc) + 4 + 2; + msnprintf((char *)temp, sizeof(temp), + "%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_XDISPLOC, + CURL_TELQUAL_IS, tn->subopt_xdisploc, CURL_IAC, CURL_SE); + bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); + if(bytes_written < 0) { + err = SOCKERRNO; + failf(data,"Sending data failed (%d)",err); + } + printsub(data, '>', &temp[2], len-2); + break; + case CURL_TELOPT_NEW_ENVIRON: + msnprintf((char *)temp, sizeof(temp), + "%c%c%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_NEW_ENVIRON, + CURL_TELQUAL_IS); + len = 4; + + for(v = tn->telnet_vars; v; v = v->next) { + size_t tmplen = (strlen(v->data) + 1); + /* Add the variable if it fits */ + if(len + tmplen < (int)sizeof(temp)-6) { + char *s = strchr(v->data, ','); + if(!s) + len += msnprintf((char *)&temp[len], sizeof(temp) - len, + "%c%s", CURL_NEW_ENV_VAR, v->data); + else { + size_t vlen = s - v->data; + len += msnprintf((char *)&temp[len], sizeof(temp) - len, + "%c%.*s%c%s", CURL_NEW_ENV_VAR, + (int)vlen, v->data, CURL_NEW_ENV_VALUE, ++s); + } + } + } + msnprintf((char *)&temp[len], sizeof(temp) - len, + "%c%c", CURL_IAC, CURL_SE); + len += 2; + bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); + if(bytes_written < 0) { + err = SOCKERRNO; + failf(data,"Sending data failed (%d)",err); + } + printsub(data, '>', &temp[2], len-2); + break; + } + return; +} + + +/* + * sendsuboption() + * + * Send suboption information to the server side. + */ + +static void sendsuboption(struct Curl_easy *data, int option) +{ + ssize_t bytes_written; + int err; + unsigned short x, y; + unsigned char *uc1, *uc2; + struct TELNET *tn = data->req.p.telnet; + struct connectdata *conn = data->conn; + + switch(option) { + case CURL_TELOPT_NAWS: + /* We prepare data to be sent */ + CURL_SB_CLEAR(tn); + CURL_SB_ACCUM(tn, CURL_IAC); + CURL_SB_ACCUM(tn, CURL_SB); + CURL_SB_ACCUM(tn, CURL_TELOPT_NAWS); + /* We must deal either with little or big endian processors */ + /* Window size must be sent according to the 'network order' */ + x = htons(tn->subopt_wsx); + y = htons(tn->subopt_wsy); + uc1 = (unsigned char *)&x; + uc2 = (unsigned char *)&y; + CURL_SB_ACCUM(tn, uc1[0]); + CURL_SB_ACCUM(tn, uc1[1]); + CURL_SB_ACCUM(tn, uc2[0]); + CURL_SB_ACCUM(tn, uc2[1]); + + CURL_SB_ACCUM(tn, CURL_IAC); + CURL_SB_ACCUM(tn, CURL_SE); + CURL_SB_TERM(tn); + /* data suboption is now ready */ + + printsub(data, '>', (unsigned char *)tn->subbuffer + 2, + CURL_SB_LEN(tn)-2); + + /* we send the header of the suboption... */ + bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer, 3); + if(bytes_written < 0) { + err = SOCKERRNO; + failf(data, "Sending data failed (%d)", err); + } + /* ... then the window size with the send_telnet_data() function + to deal with 0xFF cases ... */ + send_telnet_data(data, (char *)tn->subbuffer + 3, 4); + /* ... and the footer */ + bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer + 7, 2); + if(bytes_written < 0) { + err = SOCKERRNO; + failf(data, "Sending data failed (%d)", err); + } + break; + } +} + + +static +CURLcode telrcv(struct Curl_easy *data, + const unsigned char *inbuf, /* Data received from socket */ + ssize_t count) /* Number of bytes received */ +{ + unsigned char c; + CURLcode result; + int in = 0; + int startwrite = -1; + struct TELNET *tn = data->req.p.telnet; + +#define startskipping() \ + if(startwrite >= 0) { \ + result = Curl_client_write(data, \ + CLIENTWRITE_BODY, \ + (char *)&inbuf[startwrite], \ + in-startwrite); \ + if(result) \ + return result; \ + } \ + startwrite = -1 + +#define writebyte() \ + if(startwrite < 0) \ + startwrite = in + +#define bufferflush() startskipping() + + while(count--) { + c = inbuf[in]; + + switch(tn->telrcv_state) { + case CURL_TS_CR: + tn->telrcv_state = CURL_TS_DATA; + if(c == '\0') { + startskipping(); + break; /* Ignore \0 after CR */ + } + writebyte(); + break; + + case CURL_TS_DATA: + if(c == CURL_IAC) { + tn->telrcv_state = CURL_TS_IAC; + startskipping(); + break; + } + else if(c == '\r') + tn->telrcv_state = CURL_TS_CR; + writebyte(); + break; + + case CURL_TS_IAC: +process_iac: + DEBUGASSERT(startwrite < 0); + switch(c) { + case CURL_WILL: + tn->telrcv_state = CURL_TS_WILL; + break; + case CURL_WONT: + tn->telrcv_state = CURL_TS_WONT; + break; + case CURL_DO: + tn->telrcv_state = CURL_TS_DO; + break; + case CURL_DONT: + tn->telrcv_state = CURL_TS_DONT; + break; + case CURL_SB: + CURL_SB_CLEAR(tn); + tn->telrcv_state = CURL_TS_SB; + break; + case CURL_IAC: + tn->telrcv_state = CURL_TS_DATA; + writebyte(); + break; + case CURL_DM: + case CURL_NOP: + case CURL_GA: + default: + tn->telrcv_state = CURL_TS_DATA; + printoption(data, "RCVD", CURL_IAC, c); + break; + } + break; + + case CURL_TS_WILL: + printoption(data, "RCVD", CURL_WILL, c); + tn->please_negotiate = 1; + rec_will(data, c); + tn->telrcv_state = CURL_TS_DATA; + break; + + case CURL_TS_WONT: + printoption(data, "RCVD", CURL_WONT, c); + tn->please_negotiate = 1; + rec_wont(data, c); + tn->telrcv_state = CURL_TS_DATA; + break; + + case CURL_TS_DO: + printoption(data, "RCVD", CURL_DO, c); + tn->please_negotiate = 1; + rec_do(data, c); + tn->telrcv_state = CURL_TS_DATA; + break; + + case CURL_TS_DONT: + printoption(data, "RCVD", CURL_DONT, c); + tn->please_negotiate = 1; + rec_dont(data, c); + tn->telrcv_state = CURL_TS_DATA; + break; + + case CURL_TS_SB: + if(c == CURL_IAC) + tn->telrcv_state = CURL_TS_SE; + else + CURL_SB_ACCUM(tn, c); + break; + + case CURL_TS_SE: + if(c != CURL_SE) { + if(c != CURL_IAC) { + /* + * This is an error. We only expect to get "IAC IAC" or "IAC SE". + * Several things may have happened. An IAC was not doubled, the + * IAC SE was left off, or another option got inserted into the + * suboption are all possibilities. If we assume that the IAC was + * not doubled, and really the IAC SE was left off, we could get + * into an infinite loop here. So, instead, we terminate the + * suboption, and process the partial suboption if we can. + */ + CURL_SB_ACCUM(tn, CURL_IAC); + CURL_SB_ACCUM(tn, c); + tn->subpointer -= 2; + CURL_SB_TERM(tn); + + printoption(data, "In SUBOPTION processing, RCVD", CURL_IAC, c); + suboption(data); /* handle sub-option */ + tn->telrcv_state = CURL_TS_IAC; + goto process_iac; + } + CURL_SB_ACCUM(tn, c); + tn->telrcv_state = CURL_TS_SB; + } + else { + CURL_SB_ACCUM(tn, CURL_IAC); + CURL_SB_ACCUM(tn, CURL_SE); + tn->subpointer -= 2; + CURL_SB_TERM(tn); + suboption(data); /* handle sub-option */ + tn->telrcv_state = CURL_TS_DATA; + } + break; + } + ++in; + } + bufferflush(); + return CURLE_OK; +} + +/* Escape and send a telnet data block */ +static CURLcode send_telnet_data(struct Curl_easy *data, + char *buffer, ssize_t nread) +{ + ssize_t escapes, i, outlen; + unsigned char *outbuf = NULL; + CURLcode result = CURLE_OK; + ssize_t bytes_written, total_written; + struct connectdata *conn = data->conn; + + /* Determine size of new buffer after escaping */ + escapes = 0; + for(i = 0; i < nread; i++) + if((unsigned char)buffer[i] == CURL_IAC) + escapes++; + outlen = nread + escapes; + + if(outlen == nread) + outbuf = (unsigned char *)buffer; + else { + ssize_t j; + outbuf = malloc(nread + escapes + 1); + if(!outbuf) + return CURLE_OUT_OF_MEMORY; + + j = 0; + for(i = 0; i < nread; i++) { + outbuf[j++] = (unsigned char)buffer[i]; + if((unsigned char)buffer[i] == CURL_IAC) + outbuf[j++] = CURL_IAC; + } + outbuf[j] = '\0'; + } + + total_written = 0; + while(!result && total_written < outlen) { + /* Make sure socket is writable to avoid EWOULDBLOCK condition */ + struct pollfd pfd[1]; + pfd[0].fd = conn->sock[FIRSTSOCKET]; + pfd[0].events = POLLOUT; + switch(Curl_poll(pfd, 1, -1)) { + case -1: /* error, abort writing */ + case 0: /* timeout (will never happen) */ + result = CURLE_SEND_ERROR; + break; + default: /* write! */ + bytes_written = 0; + result = Curl_nwrite(data, FIRSTSOCKET, + outbuf + total_written, + outlen - total_written, + &bytes_written); + total_written += bytes_written; + break; + } + } + + /* Free malloc copy if escaped */ + if(outbuf != (unsigned char *)buffer) + free(outbuf); + + return result; +} + +static CURLcode telnet_done(struct Curl_easy *data, + CURLcode status, bool premature) +{ + struct TELNET *tn = data->req.p.telnet; + (void)status; /* unused */ + (void)premature; /* not used */ + + if(!tn) + return CURLE_OK; + + curl_slist_free_all(tn->telnet_vars); + tn->telnet_vars = NULL; + return CURLE_OK; +} + +static CURLcode telnet_do(struct Curl_easy *data, bool *done) +{ + CURLcode result; + struct connectdata *conn = data->conn; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; +#ifdef USE_WINSOCK + WSAEVENT event_handle; + WSANETWORKEVENTS events; + HANDLE stdin_handle; + HANDLE objs[2]; + DWORD obj_count; + DWORD wait_timeout; + DWORD readfile_read; + int err; +#else + timediff_t interval_ms; + struct pollfd pfd[2]; + int poll_cnt; + curl_off_t total_dl = 0; + curl_off_t total_ul = 0; +#endif + ssize_t nread; + struct curltime now; + bool keepon = TRUE; + char *buf = data->state.buffer; + struct TELNET *tn; + + *done = TRUE; /* unconditionally */ + + result = init_telnet(data); + if(result) + return result; + + tn = data->req.p.telnet; + + result = check_telnet_options(data); + if(result) + return result; + +#ifdef USE_WINSOCK + /* We want to wait for both stdin and the socket. Since + ** the select() function in winsock only works on sockets + ** we have to use the WaitForMultipleObjects() call. + */ + + /* First, create a sockets event object */ + event_handle = WSACreateEvent(); + if(event_handle == WSA_INVALID_EVENT) { + failf(data, "WSACreateEvent failed (%d)", SOCKERRNO); + return CURLE_FAILED_INIT; + } + + /* Tell winsock what events we want to listen to */ + if(WSAEventSelect(sockfd, event_handle, FD_READ|FD_CLOSE) == SOCKET_ERROR) { + WSACloseEvent(event_handle); + return CURLE_OK; + } + + /* The get the Windows file handle for stdin */ + stdin_handle = GetStdHandle(STD_INPUT_HANDLE); + + /* Create the list of objects to wait for */ + objs[0] = event_handle; + objs[1] = stdin_handle; + + /* If stdin_handle is a pipe, use PeekNamedPipe() method to check it, + else use the old WaitForMultipleObjects() way */ + if(GetFileType(stdin_handle) == FILE_TYPE_PIPE || + data->set.is_fread_set) { + /* Don't wait for stdin_handle, just wait for event_handle */ + obj_count = 1; + /* Check stdin_handle per 100 milliseconds */ + wait_timeout = 100; + } + else { + obj_count = 2; + wait_timeout = 1000; + } + + /* Keep on listening and act on events */ + while(keepon) { + const DWORD buf_size = (DWORD)data->set.buffer_size; + DWORD waitret = WaitForMultipleObjects(obj_count, objs, + FALSE, wait_timeout); + switch(waitret) { + + case WAIT_TIMEOUT: + { + for(;;) { + if(data->set.is_fread_set) { + size_t n; + /* read from user-supplied method */ + n = data->state.fread_func(buf, 1, buf_size, data->state.in); + if(n == CURL_READFUNC_ABORT) { + keepon = FALSE; + result = CURLE_READ_ERROR; + break; + } + + if(n == CURL_READFUNC_PAUSE) + break; + + if(n == 0) /* no bytes */ + break; + + /* fall through with number of bytes read */ + readfile_read = (DWORD)n; + } + else { + /* read from stdin */ + if(!PeekNamedPipe(stdin_handle, NULL, 0, NULL, + &readfile_read, NULL)) { + keepon = FALSE; + result = CURLE_READ_ERROR; + break; + } + + if(!readfile_read) + break; + + if(!ReadFile(stdin_handle, buf, buf_size, + &readfile_read, NULL)) { + keepon = FALSE; + result = CURLE_READ_ERROR; + break; + } + } + + result = send_telnet_data(data, buf, readfile_read); + if(result) { + keepon = FALSE; + break; + } + } + } + break; + + case WAIT_OBJECT_0 + 1: + { + if(!ReadFile(stdin_handle, buf, buf_size, + &readfile_read, NULL)) { + keepon = FALSE; + result = CURLE_READ_ERROR; + break; + } + + result = send_telnet_data(data, buf, readfile_read); + if(result) { + keepon = FALSE; + break; + } + } + break; + + case WAIT_OBJECT_0: + { + events.lNetworkEvents = 0; + if(WSAEnumNetworkEvents(sockfd, event_handle, &events) == SOCKET_ERROR) { + err = SOCKERRNO; + if(err != EINPROGRESS) { + infof(data, "WSAEnumNetworkEvents failed (%d)", err); + keepon = FALSE; + result = CURLE_READ_ERROR; + } + break; + } + if(events.lNetworkEvents & FD_READ) { + /* read data from network */ + result = Curl_read(data, sockfd, buf, data->set.buffer_size, &nread); + /* read would've blocked. Loop again */ + if(result == CURLE_AGAIN) + break; + /* returned not-zero, this an error */ + else if(result) { + keepon = FALSE; + break; + } + /* returned zero but actually received 0 or less here, + the server closed the connection and we bail out */ + else if(nread <= 0) { + keepon = FALSE; + break; + } + + result = telrcv(data, (unsigned char *) buf, nread); + if(result) { + keepon = FALSE; + break; + } + + /* Negotiate if the peer has started negotiating, + otherwise don't. We don't want to speak telnet with + non-telnet servers, like POP or SMTP. */ + if(tn->please_negotiate && !tn->already_negotiated) { + negotiate(data); + tn->already_negotiated = 1; + } + } + if(events.lNetworkEvents & FD_CLOSE) { + keepon = FALSE; + } + } + break; + + } + + if(data->set.timeout) { + now = Curl_now(); + if(Curl_timediff(now, conn->created) >= data->set.timeout) { + failf(data, "Time-out"); + result = CURLE_OPERATION_TIMEDOUT; + keepon = FALSE; + } + } + } + + /* We called WSACreateEvent, so call WSACloseEvent */ + if(!WSACloseEvent(event_handle)) { + infof(data, "WSACloseEvent failed (%d)", SOCKERRNO); + } +#else + pfd[0].fd = sockfd; + pfd[0].events = POLLIN; + + if(data->set.is_fread_set) { + poll_cnt = 1; + interval_ms = 100; /* poll user-supplied read function */ + } + else { + /* really using fread, so infile is a FILE* */ + pfd[1].fd = fileno((FILE *)data->state.in); + pfd[1].events = POLLIN; + poll_cnt = 2; + interval_ms = 1 * 1000; + } + + while(keepon) { + DEBUGF(infof(data, "telnet_do, poll %d fds", poll_cnt)); + switch(Curl_poll(pfd, poll_cnt, interval_ms)) { + case -1: /* error, stop reading */ + keepon = FALSE; + continue; + case 0: /* timeout */ + pfd[0].revents = 0; + pfd[1].revents = 0; + /* FALLTHROUGH */ + default: /* read! */ + if(pfd[0].revents & POLLIN) { + /* read data from network */ + result = Curl_read(data, sockfd, buf, data->set.buffer_size, &nread); + /* read would've blocked. Loop again */ + if(result == CURLE_AGAIN) + break; + /* returned not-zero, this an error */ + if(result) { + keepon = FALSE; + /* TODO: in test 1452, macOS sees a ECONNRESET sometimes? + * Is this the telnet test server not shutting down the socket + * in a clean way? Seems to be timing related, happens more + * on slow debug build */ + if(data->state.os_errno == ECONNRESET) { + DEBUGF(infof(data, "telnet_do, unexpected ECONNRESET on recv")); + } + break; + } + /* returned zero but actually received 0 or less here, + the server closed the connection and we bail out */ + else if(nread <= 0) { + keepon = FALSE; + break; + } + + total_dl += nread; + result = Curl_pgrsSetDownloadCounter(data, total_dl); + if(!result) + result = telrcv(data, (unsigned char *)buf, nread); + if(result) { + keepon = FALSE; + break; + } + + /* Negotiate if the peer has started negotiating, + otherwise don't. We don't want to speak telnet with + non-telnet servers, like POP or SMTP. */ + if(tn->please_negotiate && !tn->already_negotiated) { + negotiate(data); + tn->already_negotiated = 1; + } + } + + nread = 0; + if(poll_cnt == 2) { + if(pfd[1].revents & POLLIN) { /* read from in file */ + nread = read(pfd[1].fd, buf, data->set.buffer_size); + } + } + else { + /* read from user-supplied method */ + nread = (int)data->state.fread_func(buf, 1, data->set.buffer_size, + data->state.in); + if(nread == CURL_READFUNC_ABORT) { + keepon = FALSE; + break; + } + if(nread == CURL_READFUNC_PAUSE) + break; + } + + if(nread > 0) { + result = send_telnet_data(data, buf, nread); + if(result) { + keepon = FALSE; + break; + } + total_ul += nread; + Curl_pgrsSetUploadCounter(data, total_ul); + } + else if(nread < 0) + keepon = FALSE; + + break; + } /* poll switch statement */ + + if(data->set.timeout) { + now = Curl_now(); + if(Curl_timediff(now, conn->created) >= data->set.timeout) { + failf(data, "Time-out"); + result = CURLE_OPERATION_TIMEDOUT; + keepon = FALSE; + } + } + + if(Curl_pgrsUpdate(data)) { + result = CURLE_ABORTED_BY_CALLBACK; + break; + } + } +#endif + /* mark this as "no further transfer wanted" */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + + return result; +} +#endif diff --git a/Utilities/cmcurl/lib/telnet.h b/Utilities/cmcurl/lib/telnet.h new file mode 100644 index 0000000..30782d8 --- /dev/null +++ b/Utilities/cmcurl/lib/telnet.h @@ -0,0 +1,30 @@ +#ifndef HEADER_CURL_TELNET_H +#define HEADER_CURL_TELNET_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#ifndef CURL_DISABLE_TELNET +extern const struct Curl_handler Curl_handler_telnet; +#endif + +#endif /* HEADER_CURL_TELNET_H */ diff --git a/Utilities/cmcurl/lib/tftp.c b/Utilities/cmcurl/lib/tftp.c new file mode 100644 index 0000000..6630155 --- /dev/null +++ b/Utilities/cmcurl/lib/tftp.c @@ -0,0 +1,1405 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_TFTP + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#include "urldata.h" +#include <curl/curl.h> +#include "cf-socket.h" +#include "transfer.h" +#include "sendf.h" +#include "tftp.h" +#include "progress.h" +#include "connect.h" +#include "strerror.h" +#include "sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "multiif.h" +#include "url.h" +#include "strcase.h" +#include "speedcheck.h" +#include "select.h" +#include "escape.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* RFC2348 allows the block size to be negotiated */ +#define TFTP_BLKSIZE_DEFAULT 512 +#define TFTP_OPTION_BLKSIZE "blksize" + +/* from RFC2349: */ +#define TFTP_OPTION_TSIZE "tsize" +#define TFTP_OPTION_INTERVAL "timeout" + +typedef enum { + TFTP_MODE_NETASCII = 0, + TFTP_MODE_OCTET +} tftp_mode_t; + +typedef enum { + TFTP_STATE_START = 0, + TFTP_STATE_RX, + TFTP_STATE_TX, + TFTP_STATE_FIN +} tftp_state_t; + +typedef enum { + TFTP_EVENT_NONE = -1, + TFTP_EVENT_INIT = 0, + TFTP_EVENT_RRQ = 1, + TFTP_EVENT_WRQ = 2, + TFTP_EVENT_DATA = 3, + TFTP_EVENT_ACK = 4, + TFTP_EVENT_ERROR = 5, + TFTP_EVENT_OACK = 6, + TFTP_EVENT_TIMEOUT +} tftp_event_t; + +typedef enum { + TFTP_ERR_UNDEF = 0, + TFTP_ERR_NOTFOUND, + TFTP_ERR_PERM, + TFTP_ERR_DISKFULL, + TFTP_ERR_ILLEGAL, + TFTP_ERR_UNKNOWNID, + TFTP_ERR_EXISTS, + TFTP_ERR_NOSUCHUSER, /* This will never be triggered by this code */ + + /* The remaining error codes are internal to curl */ + TFTP_ERR_NONE = -100, + TFTP_ERR_TIMEOUT, + TFTP_ERR_NORESPONSE +} tftp_error_t; + +struct tftp_packet { + unsigned char *data; +}; + +struct tftp_state_data { + tftp_state_t state; + tftp_mode_t mode; + tftp_error_t error; + tftp_event_t event; + struct Curl_easy *data; + curl_socket_t sockfd; + int retries; + int retry_time; + int retry_max; + time_t rx_time; + struct Curl_sockaddr_storage local_addr; + struct Curl_sockaddr_storage remote_addr; + curl_socklen_t remote_addrlen; + int rbytes; + int sbytes; + int blksize; + int requested_blksize; + unsigned short block; + struct tftp_packet rpacket; + struct tftp_packet spacket; +}; + + +/* Forward declarations */ +static CURLcode tftp_rx(struct tftp_state_data *state, tftp_event_t event); +static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event); +static CURLcode tftp_connect(struct Curl_easy *data, bool *done); +static CURLcode tftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection); +static CURLcode tftp_do(struct Curl_easy *data, bool *done); +static CURLcode tftp_done(struct Curl_easy *data, + CURLcode, bool premature); +static CURLcode tftp_setup_connection(struct Curl_easy *data, + struct connectdata *conn); +static CURLcode tftp_multi_statemach(struct Curl_easy *data, bool *done); +static CURLcode tftp_doing(struct Curl_easy *data, bool *dophase_done); +static int tftp_getsock(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *socks); +static CURLcode tftp_translate_code(tftp_error_t error); + + +/* + * TFTP protocol handler. + */ + +const struct Curl_handler Curl_handler_tftp = { + "TFTP", /* scheme */ + tftp_setup_connection, /* setup_connection */ + tftp_do, /* do_it */ + tftp_done, /* done */ + ZERO_NULL, /* do_more */ + tftp_connect, /* connect_it */ + tftp_multi_statemach, /* connecting */ + tftp_doing, /* doing */ + tftp_getsock, /* proto_getsock */ + tftp_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + tftp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_TFTP, /* defport */ + CURLPROTO_TFTP, /* protocol */ + CURLPROTO_TFTP, /* family */ + PROTOPT_NOTCPPROXY | PROTOPT_NOURLQUERY /* flags */ +}; + +/********************************************************** + * + * tftp_set_timeouts - + * + * Set timeouts based on state machine state. + * Use user provided connect timeouts until DATA or ACK + * packet is received, then use user-provided transfer timeouts + * + * + **********************************************************/ +static CURLcode tftp_set_timeouts(struct tftp_state_data *state) +{ + time_t maxtime, timeout; + timediff_t timeout_ms; + bool start = (state->state == TFTP_STATE_START) ? TRUE : FALSE; + + /* Compute drop-dead time */ + timeout_ms = Curl_timeleft(state->data, NULL, start); + + if(timeout_ms < 0) { + /* time-out, bail out, go home */ + failf(state->data, "Connection time-out"); + return CURLE_OPERATION_TIMEDOUT; + } + + if(timeout_ms > 0) + maxtime = (time_t)(timeout_ms + 500) / 1000; + else + maxtime = 3600; /* use for calculating block timeouts */ + + /* Set per-block timeout to total */ + timeout = maxtime; + + /* Average reposting an ACK after 5 seconds */ + state->retry_max = (int)timeout/5; + + /* But bound the total number */ + if(state->retry_max<3) + state->retry_max = 3; + + if(state->retry_max>50) + state->retry_max = 50; + + /* Compute the re-ACK interval to suit the timeout */ + state->retry_time = (int)(timeout/state->retry_max); + if(state->retry_time<1) + state->retry_time = 1; + + infof(state->data, + "set timeouts for state %d; Total % " CURL_FORMAT_CURL_OFF_T + ", retry %d maxtry %d", + (int)state->state, timeout_ms, state->retry_time, state->retry_max); + + /* init RX time */ + time(&state->rx_time); + + return CURLE_OK; +} + +/********************************************************** + * + * tftp_set_send_first + * + * Event handler for the START state + * + **********************************************************/ + +static void setpacketevent(struct tftp_packet *packet, unsigned short num) +{ + packet->data[0] = (unsigned char)(num >> 8); + packet->data[1] = (unsigned char)(num & 0xff); +} + + +static void setpacketblock(struct tftp_packet *packet, unsigned short num) +{ + packet->data[2] = (unsigned char)(num >> 8); + packet->data[3] = (unsigned char)(num & 0xff); +} + +static unsigned short getrpacketevent(const struct tftp_packet *packet) +{ + return (unsigned short)((packet->data[0] << 8) | packet->data[1]); +} + +static unsigned short getrpacketblock(const struct tftp_packet *packet) +{ + return (unsigned short)((packet->data[2] << 8) | packet->data[3]); +} + +static size_t tftp_strnlen(const char *string, size_t maxlen) +{ + const char *end = memchr(string, '\0', maxlen); + return end ? (size_t) (end - string) : maxlen; +} + +static const char *tftp_option_get(const char *buf, size_t len, + const char **option, const char **value) +{ + size_t loc; + + loc = tftp_strnlen(buf, len); + loc++; /* NULL term */ + + if(loc >= len) + return NULL; + *option = buf; + + loc += tftp_strnlen(buf + loc, len-loc); + loc++; /* NULL term */ + + if(loc > len) + return NULL; + *value = &buf[strlen(*option) + 1]; + + return &buf[loc]; +} + +static CURLcode tftp_parse_option_ack(struct tftp_state_data *state, + const char *ptr, int len) +{ + const char *tmp = ptr; + struct Curl_easy *data = state->data; + + /* if OACK doesn't contain blksize option, the default (512) must be used */ + state->blksize = TFTP_BLKSIZE_DEFAULT; + + while(tmp < ptr + len) { + const char *option, *value; + + tmp = tftp_option_get(tmp, ptr + len - tmp, &option, &value); + if(!tmp) { + failf(data, "Malformed ACK packet, rejecting"); + return CURLE_TFTP_ILLEGAL; + } + + infof(data, "got option=(%s) value=(%s)", option, value); + + if(checkprefix(TFTP_OPTION_BLKSIZE, option)) { + long blksize; + + blksize = strtol(value, NULL, 10); + + if(!blksize) { + failf(data, "invalid blocksize value in OACK packet"); + return CURLE_TFTP_ILLEGAL; + } + if(blksize > TFTP_BLKSIZE_MAX) { + failf(data, "%s (%d)", "blksize is larger than max supported", + TFTP_BLKSIZE_MAX); + return CURLE_TFTP_ILLEGAL; + } + else if(blksize < TFTP_BLKSIZE_MIN) { + failf(data, "%s (%d)", "blksize is smaller than min supported", + TFTP_BLKSIZE_MIN); + return CURLE_TFTP_ILLEGAL; + } + else if(blksize > state->requested_blksize) { + /* could realloc pkt buffers here, but the spec doesn't call out + * support for the server requesting a bigger blksize than the client + * requests */ + failf(data, "%s (%ld)", + "server requested blksize larger than allocated", blksize); + return CURLE_TFTP_ILLEGAL; + } + + state->blksize = (int)blksize; + infof(data, "%s (%d) %s (%d)", "blksize parsed from OACK", + state->blksize, "requested", state->requested_blksize); + } + else if(checkprefix(TFTP_OPTION_TSIZE, option)) { + long tsize = 0; + + tsize = strtol(value, NULL, 10); + infof(data, "%s (%ld)", "tsize parsed from OACK", tsize); + + /* tsize should be ignored on upload: Who cares about the size of the + remote file? */ + if(!data->state.upload) { + if(!tsize) { + failf(data, "invalid tsize -:%s:- value in OACK packet", value); + return CURLE_TFTP_ILLEGAL; + } + Curl_pgrsSetDownloadSize(data, tsize); + } + } + } + + return CURLE_OK; +} + +static CURLcode tftp_option_add(struct tftp_state_data *state, size_t *csize, + char *buf, const char *option) +{ + if(( strlen(option) + *csize + 1) > (size_t)state->blksize) + return CURLE_TFTP_ILLEGAL; + strcpy(buf, option); + *csize += strlen(option) + 1; + return CURLE_OK; +} + +static CURLcode tftp_connect_for_tx(struct tftp_state_data *state, + tftp_event_t event) +{ + CURLcode result; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + struct Curl_easy *data = state->data; + + infof(data, "%s", "Connected for transmit"); +#endif + state->state = TFTP_STATE_TX; + result = tftp_set_timeouts(state); + if(result) + return result; + return tftp_tx(state, event); +} + +static CURLcode tftp_connect_for_rx(struct tftp_state_data *state, + tftp_event_t event) +{ + CURLcode result; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + struct Curl_easy *data = state->data; + + infof(data, "%s", "Connected for receive"); +#endif + state->state = TFTP_STATE_RX; + result = tftp_set_timeouts(state); + if(result) + return result; + return tftp_rx(state, event); +} + +static CURLcode tftp_send_first(struct tftp_state_data *state, + tftp_event_t event) +{ + size_t sbytes; + ssize_t senddata; + const char *mode = "octet"; + char *filename; + struct Curl_easy *data = state->data; + CURLcode result = CURLE_OK; + + /* Set ascii mode if -B flag was used */ + if(data->state.prefer_ascii) + mode = "netascii"; + + switch(event) { + + case TFTP_EVENT_INIT: /* Send the first packet out */ + case TFTP_EVENT_TIMEOUT: /* Resend the first packet out */ + /* Increment the retry counter, quit if over the limit */ + state->retries++; + if(state->retries>state->retry_max) { + state->error = TFTP_ERR_NORESPONSE; + state->state = TFTP_STATE_FIN; + return result; + } + + if(data->state.upload) { + /* If we are uploading, send an WRQ */ + setpacketevent(&state->spacket, TFTP_EVENT_WRQ); + state->data->req.upload_fromhere = + (char *)state->spacket.data + 4; + if(data->state.infilesize != -1) + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + else { + /* If we are downloading, send an RRQ */ + setpacketevent(&state->spacket, TFTP_EVENT_RRQ); + } + /* As RFC3617 describes the separator slash is not actually part of the + file name so we skip the always-present first letter of the path + string. */ + result = Curl_urldecode(&state->data->state.up.path[1], 0, + &filename, NULL, REJECT_ZERO); + if(result) + return result; + + if(strlen(filename) > (state->blksize - strlen(mode) - 4)) { + failf(data, "TFTP file name too long"); + free(filename); + return CURLE_TFTP_ILLEGAL; /* too long file name field */ + } + + msnprintf((char *)state->spacket.data + 2, + state->blksize, + "%s%c%s%c", filename, '\0', mode, '\0'); + sbytes = 4 + strlen(filename) + strlen(mode); + + /* optional addition of TFTP options */ + if(!data->set.tftp_no_options) { + char buf[64]; + /* add tsize option */ + if(data->state.upload && (data->state.infilesize != -1)) + msnprintf(buf, sizeof(buf), "%" CURL_FORMAT_CURL_OFF_T, + data->state.infilesize); + else + strcpy(buf, "0"); /* the destination is large enough */ + + result = tftp_option_add(state, &sbytes, + (char *)state->spacket.data + sbytes, + TFTP_OPTION_TSIZE); + if(result == CURLE_OK) + result = tftp_option_add(state, &sbytes, + (char *)state->spacket.data + sbytes, buf); + + /* add blksize option */ + msnprintf(buf, sizeof(buf), "%d", state->requested_blksize); + if(result == CURLE_OK) + result = tftp_option_add(state, &sbytes, + (char *)state->spacket.data + sbytes, + TFTP_OPTION_BLKSIZE); + if(result == CURLE_OK) + result = tftp_option_add(state, &sbytes, + (char *)state->spacket.data + sbytes, buf); + + /* add timeout option */ + msnprintf(buf, sizeof(buf), "%d", state->retry_time); + if(result == CURLE_OK) + result = tftp_option_add(state, &sbytes, + (char *)state->spacket.data + sbytes, + TFTP_OPTION_INTERVAL); + if(result == CURLE_OK) + result = tftp_option_add(state, &sbytes, + (char *)state->spacket.data + sbytes, buf); + + if(result != CURLE_OK) { + failf(data, "TFTP buffer too small for options"); + free(filename); + return CURLE_TFTP_ILLEGAL; + } + } + + /* the typecase for the 3rd argument is mostly for systems that do + not have a size_t argument, like older unixes that want an 'int' */ + senddata = sendto(state->sockfd, (void *)state->spacket.data, + (SEND_TYPE_ARG3)sbytes, 0, + &data->conn->remote_addr->sa_addr, + data->conn->remote_addr->addrlen); + if(senddata != (ssize_t)sbytes) { + char buffer[STRERROR_LEN]; + failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + } + free(filename); + break; + + case TFTP_EVENT_OACK: + if(data->state.upload) { + result = tftp_connect_for_tx(state, event); + } + else { + result = tftp_connect_for_rx(state, event); + } + break; + + case TFTP_EVENT_ACK: /* Connected for transmit */ + result = tftp_connect_for_tx(state, event); + break; + + case TFTP_EVENT_DATA: /* Connected for receive */ + result = tftp_connect_for_rx(state, event); + break; + + case TFTP_EVENT_ERROR: + state->state = TFTP_STATE_FIN; + break; + + default: + failf(state->data, "tftp_send_first: internal error"); + break; + } + + return result; +} + +/* the next blocknum is x + 1 but it needs to wrap at an unsigned 16bit + boundary */ +#define NEXT_BLOCKNUM(x) (((x) + 1)&0xffff) + +/********************************************************** + * + * tftp_rx + * + * Event handler for the RX state + * + **********************************************************/ +static CURLcode tftp_rx(struct tftp_state_data *state, + tftp_event_t event) +{ + ssize_t sbytes; + int rblock; + struct Curl_easy *data = state->data; + char buffer[STRERROR_LEN]; + + switch(event) { + + case TFTP_EVENT_DATA: + /* Is this the block we expect? */ + rblock = getrpacketblock(&state->rpacket); + if(NEXT_BLOCKNUM(state->block) == rblock) { + /* This is the expected block. Reset counters and ACK it. */ + state->retries = 0; + } + else if(state->block == rblock) { + /* This is the last recently received block again. Log it and ACK it + again. */ + infof(data, "Received last DATA packet block %d again.", rblock); + } + else { + /* totally unexpected, just log it */ + infof(data, + "Received unexpected DATA packet block %d, expecting block %d", + rblock, NEXT_BLOCKNUM(state->block)); + break; + } + + /* ACK this block. */ + state->block = (unsigned short)rblock; + setpacketevent(&state->spacket, TFTP_EVENT_ACK); + setpacketblock(&state->spacket, state->block); + sbytes = sendto(state->sockfd, (void *)state->spacket.data, + 4, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + if(sbytes < 0) { + failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + return CURLE_SEND_ERROR; + } + + /* Check if completed (That is, a less than full packet is received) */ + if(state->rbytes < (ssize_t)state->blksize + 4) { + state->state = TFTP_STATE_FIN; + } + else { + state->state = TFTP_STATE_RX; + } + time(&state->rx_time); + break; + + case TFTP_EVENT_OACK: + /* ACK option acknowledgement so we can move on to data */ + state->block = 0; + state->retries = 0; + setpacketevent(&state->spacket, TFTP_EVENT_ACK); + setpacketblock(&state->spacket, state->block); + sbytes = sendto(state->sockfd, (void *)state->spacket.data, + 4, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + if(sbytes < 0) { + failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + return CURLE_SEND_ERROR; + } + + /* we're ready to RX data */ + state->state = TFTP_STATE_RX; + time(&state->rx_time); + break; + + case TFTP_EVENT_TIMEOUT: + /* Increment the retry count and fail if over the limit */ + state->retries++; + infof(data, + "Timeout waiting for block %d ACK. Retries = %d", + NEXT_BLOCKNUM(state->block), state->retries); + if(state->retries > state->retry_max) { + state->error = TFTP_ERR_TIMEOUT; + state->state = TFTP_STATE_FIN; + } + else { + /* Resend the previous ACK */ + sbytes = sendto(state->sockfd, (void *)state->spacket.data, + 4, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + if(sbytes<0) { + failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + return CURLE_SEND_ERROR; + } + } + break; + + case TFTP_EVENT_ERROR: + setpacketevent(&state->spacket, TFTP_EVENT_ERROR); + setpacketblock(&state->spacket, state->block); + (void)sendto(state->sockfd, (void *)state->spacket.data, + 4, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + /* don't bother with the return code, but if the socket is still up we + * should be a good TFTP client and let the server know we're done */ + state->state = TFTP_STATE_FIN; + break; + + default: + failf(data, "%s", "tftp_rx: internal error"); + return CURLE_TFTP_ILLEGAL; /* not really the perfect return code for + this */ + } + return CURLE_OK; +} + +/********************************************************** + * + * tftp_tx + * + * Event handler for the TX state + * + **********************************************************/ +static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) +{ + struct Curl_easy *data = state->data; + ssize_t sbytes; + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + size_t cb; /* Bytes currently read */ + char buffer[STRERROR_LEN]; + + switch(event) { + + case TFTP_EVENT_ACK: + case TFTP_EVENT_OACK: + if(event == TFTP_EVENT_ACK) { + /* Ack the packet */ + int rblock = getrpacketblock(&state->rpacket); + + if(rblock != state->block && + /* There's a bug in tftpd-hpa that causes it to send us an ack for + * 65535 when the block number wraps to 0. So when we're expecting + * 0, also accept 65535. See + * https://www.syslinux.org/archives/2010-September/015612.html + * */ + !(state->block == 0 && rblock == 65535)) { + /* This isn't the expected block. Log it and up the retry counter */ + infof(data, "Received ACK for block %d, expecting %d", + rblock, state->block); + state->retries++; + /* Bail out if over the maximum */ + if(state->retries>state->retry_max) { + failf(data, "tftp_tx: giving up waiting for block %d ack", + state->block); + result = CURLE_SEND_ERROR; + } + else { + /* Re-send the data packet */ + sbytes = sendto(state->sockfd, (void *)state->spacket.data, + 4 + state->sbytes, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + /* Check all sbytes were sent */ + if(sbytes<0) { + failf(data, "%s", Curl_strerror(SOCKERRNO, + buffer, sizeof(buffer))); + result = CURLE_SEND_ERROR; + } + } + + return result; + } + /* This is the expected packet. Reset the counters and send the next + block */ + time(&state->rx_time); + state->block++; + } + else + state->block = 1; /* first data block is 1 when using OACK */ + + state->retries = 0; + setpacketevent(&state->spacket, TFTP_EVENT_DATA); + setpacketblock(&state->spacket, state->block); + if(state->block > 1 && state->sbytes < state->blksize) { + state->state = TFTP_STATE_FIN; + return CURLE_OK; + } + + /* TFTP considers data block size < 512 bytes as an end of session. So + * in some cases we must wait for additional data to build full (512 bytes) + * data block. + * */ + state->sbytes = 0; + state->data->req.upload_fromhere = (char *)state->spacket.data + 4; + do { + result = Curl_fillreadbuffer(data, state->blksize - state->sbytes, &cb); + if(result) + return result; + state->sbytes += (int)cb; + state->data->req.upload_fromhere += cb; + } while(state->sbytes < state->blksize && cb); + + sbytes = sendto(state->sockfd, (void *) state->spacket.data, + 4 + state->sbytes, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + /* Check all sbytes were sent */ + if(sbytes<0) { + failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + return CURLE_SEND_ERROR; + } + /* Update the progress meter */ + k->writebytecount += state->sbytes; + Curl_pgrsSetUploadCounter(data, k->writebytecount); + break; + + case TFTP_EVENT_TIMEOUT: + /* Increment the retry counter and log the timeout */ + state->retries++; + infof(data, "Timeout waiting for block %d ACK. " + " Retries = %d", NEXT_BLOCKNUM(state->block), state->retries); + /* Decide if we've had enough */ + if(state->retries > state->retry_max) { + state->error = TFTP_ERR_TIMEOUT; + state->state = TFTP_STATE_FIN; + } + else { + /* Re-send the data packet */ + sbytes = sendto(state->sockfd, (void *)state->spacket.data, + 4 + state->sbytes, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + /* Check all sbytes were sent */ + if(sbytes<0) { + failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + return CURLE_SEND_ERROR; + } + /* since this was a re-send, we remain at the still byte position */ + Curl_pgrsSetUploadCounter(data, k->writebytecount); + } + break; + + case TFTP_EVENT_ERROR: + state->state = TFTP_STATE_FIN; + setpacketevent(&state->spacket, TFTP_EVENT_ERROR); + setpacketblock(&state->spacket, state->block); + (void)sendto(state->sockfd, (void *)state->spacket.data, 4, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + /* don't bother with the return code, but if the socket is still up we + * should be a good TFTP client and let the server know we're done */ + state->state = TFTP_STATE_FIN; + break; + + default: + failf(data, "tftp_tx: internal error, event: %i", (int)(event)); + break; + } + + return result; +} + +/********************************************************** + * + * tftp_translate_code + * + * Translate internal error codes to CURL error codes + * + **********************************************************/ +static CURLcode tftp_translate_code(tftp_error_t error) +{ + CURLcode result = CURLE_OK; + + if(error != TFTP_ERR_NONE) { + switch(error) { + case TFTP_ERR_NOTFOUND: + result = CURLE_TFTP_NOTFOUND; + break; + case TFTP_ERR_PERM: + result = CURLE_TFTP_PERM; + break; + case TFTP_ERR_DISKFULL: + result = CURLE_REMOTE_DISK_FULL; + break; + case TFTP_ERR_UNDEF: + case TFTP_ERR_ILLEGAL: + result = CURLE_TFTP_ILLEGAL; + break; + case TFTP_ERR_UNKNOWNID: + result = CURLE_TFTP_UNKNOWNID; + break; + case TFTP_ERR_EXISTS: + result = CURLE_REMOTE_FILE_EXISTS; + break; + case TFTP_ERR_NOSUCHUSER: + result = CURLE_TFTP_NOSUCHUSER; + break; + case TFTP_ERR_TIMEOUT: + result = CURLE_OPERATION_TIMEDOUT; + break; + case TFTP_ERR_NORESPONSE: + result = CURLE_COULDNT_CONNECT; + break; + default: + result = CURLE_ABORTED_BY_CALLBACK; + break; + } + } + else + result = CURLE_OK; + + return result; +} + +/********************************************************** + * + * tftp_state_machine + * + * The tftp state machine event dispatcher + * + **********************************************************/ +static CURLcode tftp_state_machine(struct tftp_state_data *state, + tftp_event_t event) +{ + CURLcode result = CURLE_OK; + struct Curl_easy *data = state->data; + + switch(state->state) { + case TFTP_STATE_START: + DEBUGF(infof(data, "TFTP_STATE_START")); + result = tftp_send_first(state, event); + break; + case TFTP_STATE_RX: + DEBUGF(infof(data, "TFTP_STATE_RX")); + result = tftp_rx(state, event); + break; + case TFTP_STATE_TX: + DEBUGF(infof(data, "TFTP_STATE_TX")); + result = tftp_tx(state, event); + break; + case TFTP_STATE_FIN: + infof(data, "%s", "TFTP finished"); + break; + default: + DEBUGF(infof(data, "STATE: %d", state->state)); + failf(data, "%s", "Internal state machine error"); + result = CURLE_TFTP_ILLEGAL; + break; + } + + return result; +} + +/********************************************************** + * + * tftp_disconnect + * + * The disconnect callback + * + **********************************************************/ +static CURLcode tftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection) +{ + struct tftp_state_data *state = conn->proto.tftpc; + (void) data; + (void) dead_connection; + + /* done, free dynamically allocated pkt buffers */ + if(state) { + Curl_safefree(state->rpacket.data); + Curl_safefree(state->spacket.data); + free(state); + } + + return CURLE_OK; +} + +/********************************************************** + * + * tftp_connect + * + * The connect callback + * + **********************************************************/ +static CURLcode tftp_connect(struct Curl_easy *data, bool *done) +{ + struct tftp_state_data *state; + int blksize; + int need_blksize; + struct connectdata *conn = data->conn; + + blksize = TFTP_BLKSIZE_DEFAULT; + + state = conn->proto.tftpc = calloc(1, sizeof(struct tftp_state_data)); + if(!state) + return CURLE_OUT_OF_MEMORY; + + /* alloc pkt buffers based on specified blksize */ + if(data->set.tftp_blksize) + /* range checked when set */ + blksize = (int)data->set.tftp_blksize; + + need_blksize = blksize; + /* default size is the fallback when no OACK is received */ + if(need_blksize < TFTP_BLKSIZE_DEFAULT) + need_blksize = TFTP_BLKSIZE_DEFAULT; + + if(!state->rpacket.data) { + state->rpacket.data = calloc(1, need_blksize + 2 + 2); + + if(!state->rpacket.data) + return CURLE_OUT_OF_MEMORY; + } + + if(!state->spacket.data) { + state->spacket.data = calloc(1, need_blksize + 2 + 2); + + if(!state->spacket.data) + return CURLE_OUT_OF_MEMORY; + } + + /* we don't keep TFTP connections up basically because there's none or very + * little gain for UDP */ + connclose(conn, "TFTP"); + + state->data = data; + state->sockfd = conn->sock[FIRSTSOCKET]; + state->state = TFTP_STATE_START; + state->error = TFTP_ERR_NONE; + state->blksize = TFTP_BLKSIZE_DEFAULT; /* Unless updated by OACK response */ + state->requested_blksize = blksize; + + ((struct sockaddr *)&state->local_addr)->sa_family = + (CURL_SA_FAMILY_T)(conn->remote_addr->family); + + tftp_set_timeouts(state); + + if(!conn->bits.bound) { + /* If not already bound, bind to any interface, random UDP port. If it is + * reused or a custom local port was desired, this has already been done! + * + * We once used the size of the local_addr struct as the third argument + * for bind() to better work with IPv6 or whatever size the struct could + * have, but we learned that at least Tru64, AIX and IRIX *requires* the + * size of that argument to match the exact size of a 'sockaddr_in' struct + * when running IPv4-only. + * + * Therefore we use the size from the address we connected to, which we + * assume uses the same IP version and thus hopefully this works for both + * IPv4 and IPv6... + */ + int rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr, + conn->remote_addr->addrlen); + if(rc) { + char buffer[STRERROR_LEN]; + failf(data, "bind() failed; %s", + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + return CURLE_COULDNT_CONNECT; + } + conn->bits.bound = TRUE; + } + + Curl_pgrsStartNow(data); + + *done = TRUE; + + return CURLE_OK; +} + +/********************************************************** + * + * tftp_done + * + * The done callback + * + **********************************************************/ +static CURLcode tftp_done(struct Curl_easy *data, CURLcode status, + bool premature) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct tftp_state_data *state = conn->proto.tftpc; + + (void)status; /* unused */ + (void)premature; /* not used */ + + if(Curl_pgrsDone(data)) + return CURLE_ABORTED_BY_CALLBACK; + + /* If we have encountered an error */ + if(state) + result = tftp_translate_code(state->error); + + return result; +} + +/********************************************************** + * + * tftp_getsock + * + * The getsock callback + * + **********************************************************/ +static int tftp_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) +{ + (void)data; + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_READSOCK(0); +} + +/********************************************************** + * + * tftp_receive_packet + * + * Called once select fires and data is ready on the socket + * + **********************************************************/ +static CURLcode tftp_receive_packet(struct Curl_easy *data) +{ + struct Curl_sockaddr_storage fromaddr; + curl_socklen_t fromlen; + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct tftp_state_data *state = conn->proto.tftpc; + + /* Receive the packet */ + fromlen = sizeof(fromaddr); + state->rbytes = (int)recvfrom(state->sockfd, + (void *)state->rpacket.data, + state->blksize + 4, + 0, + (struct sockaddr *)&fromaddr, + &fromlen); + if(state->remote_addrlen == 0) { + memcpy(&state->remote_addr, &fromaddr, fromlen); + state->remote_addrlen = fromlen; + } + + /* Sanity check packet length */ + if(state->rbytes < 4) { + failf(data, "Received too short packet"); + /* Not a timeout, but how best to handle it? */ + state->event = TFTP_EVENT_TIMEOUT; + } + else { + /* The event is given by the TFTP packet time */ + unsigned short event = getrpacketevent(&state->rpacket); + state->event = (tftp_event_t)event; + + switch(state->event) { + case TFTP_EVENT_DATA: + /* Don't pass to the client empty or retransmitted packets */ + if(state->rbytes > 4 && + (NEXT_BLOCKNUM(state->block) == getrpacketblock(&state->rpacket))) { + result = Curl_client_write(data, CLIENTWRITE_BODY, + (char *)state->rpacket.data + 4, + state->rbytes-4); + if(result) { + tftp_state_machine(state, TFTP_EVENT_ERROR); + return result; + } + } + break; + case TFTP_EVENT_ERROR: + { + unsigned short error = getrpacketblock(&state->rpacket); + char *str = (char *)state->rpacket.data + 4; + size_t strn = state->rbytes - 4; + state->error = (tftp_error_t)error; + if(tftp_strnlen(str, strn) < strn) + infof(data, "TFTP error: %s", str); + break; + } + case TFTP_EVENT_ACK: + break; + case TFTP_EVENT_OACK: + result = tftp_parse_option_ack(state, + (const char *)state->rpacket.data + 2, + state->rbytes-2); + if(result) + return result; + break; + case TFTP_EVENT_RRQ: + case TFTP_EVENT_WRQ: + default: + failf(data, "%s", "Internal error: Unexpected packet"); + break; + } + + /* Update the progress meter */ + if(Curl_pgrsUpdate(data)) { + tftp_state_machine(state, TFTP_EVENT_ERROR); + return CURLE_ABORTED_BY_CALLBACK; + } + } + return result; +} + +/********************************************************** + * + * tftp_state_timeout + * + * Check if timeouts have been reached + * + **********************************************************/ +static timediff_t tftp_state_timeout(struct Curl_easy *data, + tftp_event_t *event) +{ + time_t current; + struct connectdata *conn = data->conn; + struct tftp_state_data *state = conn->proto.tftpc; + timediff_t timeout_ms; + + if(event) + *event = TFTP_EVENT_NONE; + + timeout_ms = Curl_timeleft(state->data, NULL, + (state->state == TFTP_STATE_START)); + if(timeout_ms < 0) { + state->error = TFTP_ERR_TIMEOUT; + state->state = TFTP_STATE_FIN; + return 0; + } + time(¤t); + if(current > state->rx_time + state->retry_time) { + if(event) + *event = TFTP_EVENT_TIMEOUT; + time(&state->rx_time); /* update even though we received nothing */ + } + + return timeout_ms; +} + +/********************************************************** + * + * tftp_multi_statemach + * + * Handle single RX socket event and return + * + **********************************************************/ +static CURLcode tftp_multi_statemach(struct Curl_easy *data, bool *done) +{ + tftp_event_t event; + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct tftp_state_data *state = conn->proto.tftpc; + timediff_t timeout_ms = tftp_state_timeout(data, &event); + + *done = FALSE; + + if(timeout_ms < 0) { + failf(data, "TFTP response timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + if(event != TFTP_EVENT_NONE) { + result = tftp_state_machine(state, event); + if(result) + return result; + *done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE; + if(*done) + /* Tell curl we're done */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + } + else { + /* no timeouts to handle, check our socket */ + int rc = SOCKET_READABLE(state->sockfd, 0); + + if(rc == -1) { + /* bail out */ + int error = SOCKERRNO; + char buffer[STRERROR_LEN]; + failf(data, "%s", Curl_strerror(error, buffer, sizeof(buffer))); + state->event = TFTP_EVENT_ERROR; + } + else if(rc) { + result = tftp_receive_packet(data); + if(result) + return result; + result = tftp_state_machine(state, state->event); + if(result) + return result; + *done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE; + if(*done) + /* Tell curl we're done */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + } + /* if rc == 0, then select() timed out */ + } + + return result; +} + +/********************************************************** + * + * tftp_doing + * + * Called from multi.c while DOing + * + **********************************************************/ +static CURLcode tftp_doing(struct Curl_easy *data, bool *dophase_done) +{ + CURLcode result; + result = tftp_multi_statemach(data, dophase_done); + + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete")); + } + else if(!result) { + /* The multi code doesn't have this logic for the DOING state so we + provide it for TFTP since it may do the entire transfer in this + state. */ + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, Curl_now()); + } + return result; +} + +/********************************************************** + * + * tftp_perform + * + * Entry point for transfer from tftp_do, starts state mach + * + **********************************************************/ +static CURLcode tftp_perform(struct Curl_easy *data, bool *dophase_done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct tftp_state_data *state = conn->proto.tftpc; + + *dophase_done = FALSE; + + result = tftp_state_machine(state, TFTP_EVENT_INIT); + + if((state->state == TFTP_STATE_FIN) || result) + return result; + + tftp_multi_statemach(data, dophase_done); + + if(*dophase_done) + DEBUGF(infof(data, "DO phase is complete")); + + return result; +} + + +/********************************************************** + * + * tftp_do + * + * The do callback + * + * This callback initiates the TFTP transfer + * + **********************************************************/ + +static CURLcode tftp_do(struct Curl_easy *data, bool *done) +{ + struct tftp_state_data *state; + CURLcode result; + struct connectdata *conn = data->conn; + + *done = FALSE; + + if(!conn->proto.tftpc) { + result = tftp_connect(data, done); + if(result) + return result; + } + + state = conn->proto.tftpc; + if(!state) + return CURLE_TFTP_ILLEGAL; + + result = tftp_perform(data, done); + + /* If tftp_perform() returned an error, use that for return code. If it + was OK, see if tftp_translate_code() has an error. */ + if(!result) + /* If we have encountered an internal tftp error, translate it. */ + result = tftp_translate_code(state->error); + + return result; +} + +static CURLcode tftp_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + char *type; + + conn->transport = TRNSPRT_UDP; + + /* TFTP URLs support an extension like ";mode=<typecode>" that + * we'll try to get now! */ + type = strstr(data->state.up.path, ";mode="); + + if(!type) + type = strstr(conn->host.rawalloc, ";mode="); + + if(type) { + char command; + *type = 0; /* it was in the middle of the hostname */ + command = Curl_raw_toupper(type[6]); + + switch(command) { + case 'A': /* ASCII mode */ + case 'N': /* NETASCII mode */ + data->state.prefer_ascii = TRUE; + break; + + case 'O': /* octet mode */ + case 'I': /* binary mode */ + default: + /* switch off ASCII */ + data->state.prefer_ascii = FALSE; + break; + } + } + + return CURLE_OK; +} +#endif diff --git a/Utilities/cmcurl/lib/tftp.h b/Utilities/cmcurl/lib/tftp.h new file mode 100644 index 0000000..12404bf --- /dev/null +++ b/Utilities/cmcurl/lib/tftp.h @@ -0,0 +1,33 @@ +#ifndef HEADER_CURL_TFTP_H +#define HEADER_CURL_TFTP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#ifndef CURL_DISABLE_TFTP +extern const struct Curl_handler Curl_handler_tftp; + +#define TFTP_BLKSIZE_MIN 8 +#define TFTP_BLKSIZE_MAX 65464 +#endif + +#endif /* HEADER_CURL_TFTP_H */ diff --git a/Utilities/cmcurl/lib/timediff.c b/Utilities/cmcurl/lib/timediff.c new file mode 100644 index 0000000..d0824d1 --- /dev/null +++ b/Utilities/cmcurl/lib/timediff.c @@ -0,0 +1,88 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "timediff.h" + +#include <limits.h> + +/* + * Converts number of milliseconds into a timeval structure. + * + * Return values: + * NULL IF tv is NULL or ms < 0 (eg. no timeout -> blocking select) + * tv with 0 in both fields IF ms == 0 (eg. 0ms timeout -> polling select) + * tv with converted fields IF ms > 0 (eg. >0ms timeout -> waiting select) + */ +struct timeval *curlx_mstotv(struct timeval *tv, timediff_t ms) +{ + if(!tv) + return NULL; + + if(ms < 0) + return NULL; + + if(ms > 0) { + timediff_t tv_sec = ms / 1000; + timediff_t tv_usec = (ms % 1000) * 1000; /* max=999999 */ +#ifdef HAVE_SUSECONDS_T +#if TIMEDIFF_T_MAX > TIME_T_MAX + /* tv_sec overflow check in case time_t is signed */ + if(tv_sec > TIME_T_MAX) + tv_sec = TIME_T_MAX; +#endif + tv->tv_sec = (time_t)tv_sec; + tv->tv_usec = (suseconds_t)tv_usec; +#elif defined(_WIN32) /* maybe also others in the future */ +#if TIMEDIFF_T_MAX > LONG_MAX + /* tv_sec overflow check on Windows there we know it is long */ + if(tv_sec > LONG_MAX) + tv_sec = LONG_MAX; +#endif + tv->tv_sec = (long)tv_sec; + tv->tv_usec = (long)tv_usec; +#else +#if TIMEDIFF_T_MAX > INT_MAX + /* tv_sec overflow check in case time_t is signed */ + if(tv_sec > INT_MAX) + tv_sec = INT_MAX; +#endif + tv->tv_sec = (int)tv_sec; + tv->tv_usec = (int)tv_usec; +#endif + } + else { + tv->tv_sec = 0; + tv->tv_usec = 0; + } + + return tv; +} + +/* + * Converts a timeval structure into number of milliseconds. + */ +timediff_t curlx_tvtoms(struct timeval *tv) +{ + return (tv->tv_sec*1000) + (timediff_t)(((double)tv->tv_usec)/1000.0); +} diff --git a/Utilities/cmcurl/lib/timediff.h b/Utilities/cmcurl/lib/timediff.h new file mode 100644 index 0000000..fb318d4 --- /dev/null +++ b/Utilities/cmcurl/lib/timediff.h @@ -0,0 +1,52 @@ +#ifndef HEADER_CURL_TIMEDIFF_H +#define HEADER_CURL_TIMEDIFF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +/* Use a larger type even for 32 bit time_t systems so that we can keep + microsecond accuracy in it */ +typedef curl_off_t timediff_t; +#define CURL_FORMAT_TIMEDIFF_T CURL_FORMAT_CURL_OFF_T + +#define TIMEDIFF_T_MAX CURL_OFF_T_MAX +#define TIMEDIFF_T_MIN CURL_OFF_T_MIN + +/* + * Converts number of milliseconds into a timeval structure. + * + * Return values: + * NULL IF tv is NULL or ms < 0 (eg. no timeout -> blocking select) + * tv with 0 in both fields IF ms == 0 (eg. 0ms timeout -> polling select) + * tv with converted fields IF ms > 0 (eg. >0ms timeout -> waiting select) + */ +struct timeval *curlx_mstotv(struct timeval *tv, timediff_t ms); + +/* + * Converts a timeval structure into number of milliseconds. + */ +timediff_t curlx_tvtoms(struct timeval *tv); + +#endif /* HEADER_CURL_TIMEDIFF_H */ diff --git a/Utilities/cmcurl/lib/timeval.c b/Utilities/cmcurl/lib/timeval.c new file mode 100644 index 0000000..5a6727c --- /dev/null +++ b/Utilities/cmcurl/lib/timeval.c @@ -0,0 +1,237 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "timeval.h" + +#if defined(_WIN32) + +#include <curl/curl.h> +#include "system_win32.h" + +/* In case of bug fix this function has a counterpart in tool_util.c */ +struct curltime Curl_now(void) +{ + struct curltime now; + if(Curl_isVistaOrGreater) { /* QPC timer might have issues pre-Vista */ + LARGE_INTEGER count; + QueryPerformanceCounter(&count); + now.tv_sec = (time_t)(count.QuadPart / Curl_freq.QuadPart); + now.tv_usec = (int)((count.QuadPart % Curl_freq.QuadPart) * 1000000 / + Curl_freq.QuadPart); + } + else { + /* Disable /analyze warning that GetTickCount64 is preferred */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:28159) +#endif + DWORD milliseconds = GetTickCount(); +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + + now.tv_sec = milliseconds / 1000; + now.tv_usec = (milliseconds % 1000) * 1000; + } + return now; +} + +#elif defined(HAVE_CLOCK_GETTIME_MONOTONIC) || \ + defined(HAVE_CLOCK_GETTIME_MONOTONIC_RAW) + +struct curltime Curl_now(void) +{ + /* + ** clock_gettime() is granted to be increased monotonically when the + ** monotonic clock is queried. Time starting point is unspecified, it + ** could be the system start-up time, the Epoch, or something else, + ** in any case the time starting point does not change once that the + ** system has started up. + */ +#ifdef HAVE_GETTIMEOFDAY + struct timeval now; +#endif + struct curltime cnow; + struct timespec tsnow; + + /* + ** clock_gettime() may be defined by Apple's SDK as weak symbol thus + ** code compiles but fails during run-time if clock_gettime() is + ** called on unsupported OS version. + */ +#if defined(__APPLE__) && defined(HAVE_BUILTIN_AVAILABLE) && \ + (HAVE_BUILTIN_AVAILABLE == 1) + bool have_clock_gettime = FALSE; + if(__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) + have_clock_gettime = TRUE; +#endif + +#ifdef HAVE_CLOCK_GETTIME_MONOTONIC_RAW + if( +#if defined(__APPLE__) && defined(HAVE_BUILTIN_AVAILABLE) && \ + (HAVE_BUILTIN_AVAILABLE == 1) + have_clock_gettime && +#endif + (0 == clock_gettime(CLOCK_MONOTONIC_RAW, &tsnow))) { + cnow.tv_sec = tsnow.tv_sec; + cnow.tv_usec = (unsigned int)(tsnow.tv_nsec / 1000); + } + else +#endif + + if( +#if defined(__APPLE__) && defined(HAVE_BUILTIN_AVAILABLE) && \ + (HAVE_BUILTIN_AVAILABLE == 1) + have_clock_gettime && +#endif + (0 == clock_gettime(CLOCK_MONOTONIC, &tsnow))) { + cnow.tv_sec = tsnow.tv_sec; + cnow.tv_usec = (unsigned int)(tsnow.tv_nsec / 1000); + } + /* + ** Even when the configure process has truly detected monotonic clock + ** availability, it might happen that it is not actually available at + ** run-time. When this occurs simply fallback to other time source. + */ +#ifdef HAVE_GETTIMEOFDAY + else { + (void)gettimeofday(&now, NULL); + cnow.tv_sec = now.tv_sec; + cnow.tv_usec = (unsigned int)now.tv_usec; + } +#else + else { + cnow.tv_sec = time(NULL); + cnow.tv_usec = 0; + } +#endif + return cnow; +} + +#elif defined(HAVE_MACH_ABSOLUTE_TIME) + +#include <stdint.h> +#include <mach/mach_time.h> + +struct curltime Curl_now(void) +{ + /* + ** Monotonic timer on Mac OS is provided by mach_absolute_time(), which + ** returns time in Mach "absolute time units," which are platform-dependent. + ** To convert to nanoseconds, one must use conversion factors specified by + ** mach_timebase_info(). + */ + static mach_timebase_info_data_t timebase; + struct curltime cnow; + uint64_t usecs; + + if(0 == timebase.denom) + (void) mach_timebase_info(&timebase); + + usecs = mach_absolute_time(); + usecs *= timebase.numer; + usecs /= timebase.denom; + usecs /= 1000; + + cnow.tv_sec = usecs / 1000000; + cnow.tv_usec = (int)(usecs % 1000000); + + return cnow; +} + +#elif defined(HAVE_GETTIMEOFDAY) + +struct curltime Curl_now(void) +{ + /* + ** gettimeofday() is not granted to be increased monotonically, due to + ** clock drifting and external source time synchronization it can jump + ** forward or backward in time. + */ + struct timeval now; + struct curltime ret; + (void)gettimeofday(&now, NULL); + ret.tv_sec = now.tv_sec; + ret.tv_usec = (int)now.tv_usec; + return ret; +} + +#else + +struct curltime Curl_now(void) +{ + /* + ** time() returns the value of time in seconds since the Epoch. + */ + struct curltime now; + now.tv_sec = time(NULL); + now.tv_usec = 0; + return now; +} + +#endif + +/* + * Returns: time difference in number of milliseconds. For too large diffs it + * returns max value. + * + * @unittest: 1323 + */ +timediff_t Curl_timediff(struct curltime newer, struct curltime older) +{ + timediff_t diff = (timediff_t)newer.tv_sec-older.tv_sec; + if(diff >= (TIMEDIFF_T_MAX/1000)) + return TIMEDIFF_T_MAX; + else if(diff <= (TIMEDIFF_T_MIN/1000)) + return TIMEDIFF_T_MIN; + return diff * 1000 + (newer.tv_usec-older.tv_usec)/1000; +} + +/* + * Returns: time difference in number of milliseconds, rounded up. + * For too large diffs it returns max value. + */ +timediff_t Curl_timediff_ceil(struct curltime newer, struct curltime older) +{ + timediff_t diff = (timediff_t)newer.tv_sec-older.tv_sec; + if(diff >= (TIMEDIFF_T_MAX/1000)) + return TIMEDIFF_T_MAX; + else if(diff <= (TIMEDIFF_T_MIN/1000)) + return TIMEDIFF_T_MIN; + return diff * 1000 + (newer.tv_usec - older.tv_usec + 999)/1000; +} + +/* + * Returns: time difference in number of microseconds. For too large diffs it + * returns max value. + */ +timediff_t Curl_timediff_us(struct curltime newer, struct curltime older) +{ + timediff_t diff = (timediff_t)newer.tv_sec-older.tv_sec; + if(diff >= (TIMEDIFF_T_MAX/1000000)) + return TIMEDIFF_T_MAX; + else if(diff <= (TIMEDIFF_T_MIN/1000000)) + return TIMEDIFF_T_MIN; + return diff * 1000000 + newer.tv_usec-older.tv_usec; +} diff --git a/Utilities/cmcurl/lib/timeval.h b/Utilities/cmcurl/lib/timeval.h new file mode 100644 index 0000000..33dfb5b --- /dev/null +++ b/Utilities/cmcurl/lib/timeval.h @@ -0,0 +1,62 @@ +#ifndef HEADER_CURL_TIMEVAL_H +#define HEADER_CURL_TIMEVAL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "timediff.h" + +struct curltime { + time_t tv_sec; /* seconds */ + int tv_usec; /* microseconds */ +}; + +struct curltime Curl_now(void); + +/* + * 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... + * + * Returns: the time difference in number of milliseconds. + */ +timediff_t Curl_timediff(struct curltime newer, struct curltime older); + +/* + * 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... + * + * Returns: the time difference in number of milliseconds, rounded up. + */ +timediff_t Curl_timediff_ceil(struct curltime newer, struct curltime older); + +/* + * 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... + * + * Returns: the time difference in number of microseconds. + */ +timediff_t Curl_timediff_us(struct curltime newer, struct curltime older); + +#endif /* HEADER_CURL_TIMEVAL_H */ diff --git a/Utilities/cmcurl/lib/transfer.c b/Utilities/cmcurl/lib/transfer.c new file mode 100644 index 0000000..96f1fde --- /dev/null +++ b/Utilities/cmcurl/lib/transfer.c @@ -0,0 +1,1919 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "strtoofft.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#include <signal.h> + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#elif defined(HAVE_UNISTD_H) +#include <unistd.h> +#endif + +#ifndef HAVE_SOCKET +#error "We can't compile without socket() support!" +#endif + +#include "urldata.h" +#include <curl/curl.h> +#include "netrc.h" + +#include "content_encoding.h" +#include "hostip.h" +#include "cfilters.h" +#include "transfer.h" +#include "sendf.h" +#include "speedcheck.h" +#include "progress.h" +#include "http.h" +#include "url.h" +#include "getinfo.h" +#include "vtls/vtls.h" +#include "vquic/vquic.h" +#include "select.h" +#include "multiif.h" +#include "connect.h" +#include "http2.h" +#include "mime.h" +#include "strcase.h" +#include "urlapi-int.h" +#include "hsts.h" +#include "setopt.h" +#include "headers.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_IMAP) +/* + * checkheaders() checks the linked list of custom headers for a + * particular header (prefix). Provide the prefix without colon! + * + * Returns a pointer to the first matching header or NULL if none matched. + */ +char *Curl_checkheaders(const struct Curl_easy *data, + const char *thisheader, + const size_t thislen) +{ + struct curl_slist *head; + DEBUGASSERT(thislen); + DEBUGASSERT(thisheader[thislen-1] != ':'); + + for(head = data->set.headers; head; head = head->next) { + if(strncasecompare(head->data, thisheader, thislen) && + Curl_headersep(head->data[thislen]) ) + return head->data; + } + + return NULL; +} +#endif + +CURLcode Curl_get_upload_buffer(struct Curl_easy *data) +{ + if(!data->state.ulbuf) { + data->state.ulbuf = malloc(data->set.upload_buffer_size); + if(!data->state.ulbuf) + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; +} + +#ifndef CURL_DISABLE_HTTP +/* + * This function will be called to loop through the trailers buffer + * until no more data is available for sending. + */ +static size_t trailers_read(char *buffer, size_t size, size_t nitems, + void *raw) +{ + struct Curl_easy *data = (struct Curl_easy *)raw; + struct dynbuf *trailers_buf = &data->state.trailers_buf; + size_t bytes_left = Curl_dyn_len(trailers_buf) - + data->state.trailers_bytes_sent; + size_t to_copy = (size*nitems < bytes_left) ? size*nitems : bytes_left; + if(to_copy) { + memcpy(buffer, + Curl_dyn_ptr(trailers_buf) + data->state.trailers_bytes_sent, + to_copy); + data->state.trailers_bytes_sent += to_copy; + } + return to_copy; +} + +static size_t trailers_left(void *raw) +{ + struct Curl_easy *data = (struct Curl_easy *)raw; + struct dynbuf *trailers_buf = &data->state.trailers_buf; + return Curl_dyn_len(trailers_buf) - data->state.trailers_bytes_sent; +} +#endif + +/* + * This function will call the read callback to fill our buffer with data + * to upload. + */ +CURLcode Curl_fillreadbuffer(struct Curl_easy *data, size_t bytes, + size_t *nreadp) +{ + size_t buffersize = bytes; + size_t nread; + curl_read_callback readfunc = NULL; + void *extra_data = NULL; + int eof_index = 0; + +#ifndef CURL_DISABLE_HTTP + if(data->state.trailers_state == TRAILERS_INITIALIZED) { + struct curl_slist *trailers = NULL; + CURLcode result; + int trailers_ret_code; + + /* at this point we already verified that the callback exists + so we compile and store the trailers buffer, then proceed */ + infof(data, + "Moving trailers state machine from initialized to sending."); + data->state.trailers_state = TRAILERS_SENDING; + Curl_dyn_init(&data->state.trailers_buf, DYN_TRAILERS); + + data->state.trailers_bytes_sent = 0; + Curl_set_in_callback(data, true); + trailers_ret_code = data->set.trailer_callback(&trailers, + data->set.trailer_data); + Curl_set_in_callback(data, false); + if(trailers_ret_code == CURL_TRAILERFUNC_OK) { + result = Curl_http_compile_trailers(trailers, &data->state.trailers_buf, + data); + } + else { + failf(data, "operation aborted by trailing headers callback"); + *nreadp = 0; + result = CURLE_ABORTED_BY_CALLBACK; + } + if(result) { + Curl_dyn_free(&data->state.trailers_buf); + curl_slist_free_all(trailers); + return result; + } + infof(data, "Successfully compiled trailers."); + curl_slist_free_all(trailers); + } +#endif + +#ifndef CURL_DISABLE_HTTP + /* if we are transmitting trailing data, we don't need to write + a chunk size so we skip this */ + if(data->req.upload_chunky && + data->state.trailers_state == TRAILERS_NONE) { + /* if chunked Transfer-Encoding */ + buffersize -= (8 + 2 + 2); /* 32bit hex + CRLF + CRLF */ + data->req.upload_fromhere += (8 + 2); /* 32bit hex + CRLF */ + } + + if(data->state.trailers_state == TRAILERS_SENDING) { + /* if we're here then that means that we already sent the last empty chunk + but we didn't send a final CR LF, so we sent 0 CR LF. We then start + pulling trailing data until we have no more at which point we + simply return to the previous point in the state machine as if + nothing happened. + */ + readfunc = trailers_read; + extra_data = (void *)data; + eof_index = 1; + } + else +#endif + { + readfunc = data->state.fread_func; + extra_data = data->state.in; + } + + if(!data->req.fread_eof[eof_index]) { + Curl_set_in_callback(data, true); + nread = readfunc(data->req.upload_fromhere, 1, buffersize, extra_data); + Curl_set_in_callback(data, false); + /* make sure the callback is not called again after EOF */ + data->req.fread_eof[eof_index] = !nread; + } + else + nread = 0; + + if(nread == CURL_READFUNC_ABORT) { + failf(data, "operation aborted by callback"); + *nreadp = 0; + return CURLE_ABORTED_BY_CALLBACK; + } + if(nread == CURL_READFUNC_PAUSE) { + struct SingleRequest *k = &data->req; + + if(data->conn->handler->flags & PROTOPT_NONETWORK) { + /* protocols that work without network cannot be paused. This is + actually only FILE:// just now, and it can't pause since the transfer + isn't done using the "normal" procedure. */ + failf(data, "Read callback asked for PAUSE when not supported"); + return CURLE_READ_ERROR; + } + + /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */ + k->keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */ + if(data->req.upload_chunky) { + /* Back out the preallocation done above */ + data->req.upload_fromhere -= (8 + 2); + } + *nreadp = 0; + + return CURLE_OK; /* nothing was read */ + } + else if(nread > buffersize) { + /* the read function returned a too large value */ + *nreadp = 0; + failf(data, "read function returned funny value"); + return CURLE_READ_ERROR; + } + +#ifndef CURL_DISABLE_HTTP + if(!data->req.forbidchunk && data->req.upload_chunky) { + /* if chunked Transfer-Encoding + * build chunk: + * + * <HEX SIZE> CRLF + * <DATA> CRLF + */ + /* On non-ASCII platforms the <DATA> may or may not be + translated based on state.prefer_ascii while the protocol + portion must always be translated to the network encoding. + To further complicate matters, line end conversion might be + done later on, so we need to prevent CRLFs from becoming + CRCRLFs if that's the case. To do this we use bare LFs + here, knowing they'll become CRLFs later on. + */ + + bool added_crlf = FALSE; + int hexlen = 0; + const char *endofline_native; + const char *endofline_network; + + if( +#ifdef CURL_DO_LINEEND_CONV + (data->state.prefer_ascii) || +#endif + (data->set.crlf)) { + /* \n will become \r\n later on */ + endofline_native = "\n"; + endofline_network = "\x0a"; + } + else { + endofline_native = "\r\n"; + endofline_network = "\x0d\x0a"; + } + + /* if we're not handling trailing data, proceed as usual */ + if(data->state.trailers_state != TRAILERS_SENDING) { + char hexbuffer[11] = ""; + hexlen = msnprintf(hexbuffer, sizeof(hexbuffer), + "%zx%s", nread, endofline_native); + + /* move buffer pointer */ + data->req.upload_fromhere -= hexlen; + nread += hexlen; + + /* copy the prefix to the buffer, leaving out the NUL */ + memcpy(data->req.upload_fromhere, hexbuffer, hexlen); + + /* always append ASCII CRLF to the data unless + we have a valid trailer callback */ + if((nread-hexlen) == 0 && + data->set.trailer_callback != NULL && + data->state.trailers_state == TRAILERS_NONE) { + data->state.trailers_state = TRAILERS_INITIALIZED; + } + else { + memcpy(data->req.upload_fromhere + nread, + endofline_network, + strlen(endofline_network)); + added_crlf = TRUE; + } + } + + if(data->state.trailers_state == TRAILERS_SENDING && + !trailers_left(data)) { + Curl_dyn_free(&data->state.trailers_buf); + data->state.trailers_state = TRAILERS_DONE; + data->set.trailer_data = NULL; + data->set.trailer_callback = NULL; + /* mark the transfer as done */ + data->req.upload_done = TRUE; + infof(data, "Signaling end of chunked upload after trailers."); + } + else + if((nread - hexlen) == 0 && + data->state.trailers_state != TRAILERS_INITIALIZED) { + /* mark this as done once this chunk is transferred */ + data->req.upload_done = TRUE; + infof(data, + "Signaling end of chunked upload via terminating chunk."); + } + + if(added_crlf) + nread += strlen(endofline_network); /* for the added end of line */ + } +#endif + + *nreadp = nread; + + return CURLE_OK; +} + +static int data_pending(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + + if(conn->handler->protocol&PROTO_FAMILY_FTP) + return Curl_conn_data_pending(data, SECONDARYSOCKET); + + /* in the case of libssh2, we can never be really sure that we have emptied + its internal buffers so we MUST always try until we get EAGAIN back */ + return conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP) || + Curl_conn_data_pending(data, FIRSTSOCKET); +} + +/* + * Check to see if CURLOPT_TIMECONDITION was met by comparing the time of the + * remote document with the time provided by CURLOPT_TIMEVAL + */ +bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc) +{ + if((timeofdoc == 0) || (data->set.timevalue == 0)) + return TRUE; + + switch(data->set.timecondition) { + case CURL_TIMECOND_IFMODSINCE: + default: + if(timeofdoc <= data->set.timevalue) { + infof(data, + "The requested document is not new enough"); + data->info.timecond = TRUE; + return FALSE; + } + break; + case CURL_TIMECOND_IFUNMODSINCE: + if(timeofdoc >= data->set.timevalue) { + infof(data, + "The requested document is not old enough"); + data->info.timecond = TRUE; + return FALSE; + } + break; + } + + return TRUE; +} + +/* + * Go ahead and do a read if we have a readable socket or if + * the stream was rewound (in which case we have data in a + * buffer) + * + * return '*comeback' TRUE if we didn't properly drain the socket so this + * function should get called again without select() or similar in between! + */ +static CURLcode readwrite_data(struct Curl_easy *data, + struct connectdata *conn, + struct SingleRequest *k, + int *didwhat, bool *done, + bool *comeback) +{ + CURLcode result = CURLE_OK; + char *buf; + size_t blen; + size_t consumed; + int maxloops = 100; + curl_off_t max_recv = data->set.max_recv_speed? + data->set.max_recv_speed : CURL_OFF_T_MAX; + bool data_eof_handled = FALSE; + + DEBUGASSERT(data->state.buffer); + *done = FALSE; + *comeback = FALSE; + + /* This is where we loop until we have read everything there is to + read or we get a CURLE_AGAIN */ + do { + bool is_empty_data = FALSE; + size_t bytestoread = data->set.buffer_size; + /* For HTTP/2 and HTTP/3, read data without caring about the content + length. This is safe because body in HTTP/2 is always segmented + thanks to its framing layer. Meanwhile, we have to call Curl_read + to ensure that http2_handle_stream_close is called when we read all + incoming bytes for a particular stream. */ + bool is_http3 = Curl_conn_is_http3(data, conn, FIRSTSOCKET); + data_eof_handled = is_http3 || Curl_conn_is_http2(data, conn, FIRSTSOCKET); + + /* Each loop iteration starts with a fresh buffer and handles + * all data read into it. */ + buf = data->state.buffer; + blen = 0; + + /* If we are reading BODY data and the connection does NOT handle EOF + * and we know the size of the BODY data, limit the read amount */ + if(!k->header && !data_eof_handled && k->size != -1) { + curl_off_t totalleft = k->size - k->bytecount; + if(totalleft <= 0) + bytestoread = 0; + else if(totalleft < (curl_off_t)bytestoread) + bytestoread = (size_t)totalleft; + } + + if(bytestoread) { + /* receive data from the network! */ + ssize_t nread; /* number of bytes read */ + result = Curl_read(data, conn->sockfd, buf, bytestoread, &nread); + if(CURLE_AGAIN == result) { + result = CURLE_OK; + break; /* get out of loop */ + } + else if(result) + goto out; + DEBUGASSERT(nread >= 0); + blen = (size_t)nread; + } + else { + /* read nothing but since we wanted nothing we consider this an OK + situation to proceed from */ + DEBUGF(infof(data, "readwrite_data: we're done")); + } + + if(!k->bytecount) { + Curl_pgrsTime(data, TIMER_STARTTRANSFER); + if(k->exp100 > EXP100_SEND_DATA) + /* set time stamp to compare with when waiting for the 100 */ + k->start100 = Curl_now(); + } + + *didwhat |= KEEP_RECV; + /* indicates data of zero size, i.e. empty file */ + is_empty_data = ((blen == 0) && (k->bodywrites == 0)) ? TRUE : FALSE; + + if(0 < blen || is_empty_data) { + /* data->state.buffer is allocated 1 byte larger than + * data->set.buffer_size admits. *wink* */ + /* TODO: we should really not rely on this being 0-terminated, since + * the actual data read might contain 0s. */ + buf[blen] = 0; + } + + if(!blen) { + /* if we receive 0 or less here, either the data transfer is done or the + server closed the connection and we bail out from this! */ + if(data_eof_handled) + DEBUGF(infof(data, "nread == 0, stream closed, bailing")); + else + DEBUGF(infof(data, "nread <= 0, server closed connection, bailing")); + k->keepon = 0; /* stop sending as well */ + if(!is_empty_data) + break; + } + + if(conn->handler->readwrite) { + bool readmore = FALSE; /* indicates data is incomplete, need more */ + consumed = 0; + result = conn->handler->readwrite(data, conn, buf, blen, + &consumed, &readmore); + if(result) + goto out; + if(readmore) + break; + buf += consumed; + blen -= consumed; + if(k->download_done) { + /* We've stopped dealing with input, get out of the do-while loop */ + if(blen > 0) { + infof(data, + "Excess found:" + " excess = %zu" + " url = %s (zero-length body)", + blen, data->state.up.path); + } + + /* we make sure that this socket isn't read more now */ + k->keepon &= ~KEEP_RECV; + break; + } + } + +#ifndef CURL_DISABLE_HTTP + /* Since this is a two-state thing, we check if we are parsing + headers at the moment or not. */ + if(k->header) { + consumed = 0; + result = Curl_http_readwrite_headers(data, conn, buf, blen, &consumed); + if(result) + goto out; + buf += consumed; + blen -= consumed; + + if(conn->handler->readwrite && + (k->maxdownload <= 0 && blen > 0)) { + bool readmore = FALSE; /* indicates data is incomplete, need more */ + consumed = 0; + result = conn->handler->readwrite(data, conn, buf, blen, + &consumed, &readmore); + if(result) + goto out; + if(readmore) + break; + buf += consumed; + blen -= consumed; + } + + if(k->download_done) { + /* We've stopped dealing with input, get out of the do-while loop */ + if(blen > 0) { + infof(data, + "Excess found:" + " excess = %zu" + " url = %s (zero-length body)", + blen, data->state.up.path); + } + + /* we make sure that this socket isn't read more now */ + k->keepon &= ~KEEP_RECV; + break; + } + } +#endif /* CURL_DISABLE_HTTP */ + + + /* This is not an 'else if' since it may be a rest from the header + parsing, where the beginning of the buffer is headers and the end + is non-headers. */ + if(!k->header && (blen > 0 || is_empty_data)) { + + if(data->req.no_body && blen > 0) { + /* data arrives although we want none, bail out */ + streamclose(conn, "ignoring body"); + DEBUGF(infof(data, "did not want a BODY, but seeing %zu bytes", + blen)); + *done = TRUE; + result = CURLE_WEIRD_SERVER_REPLY; + goto out; + } + +#ifndef CURL_DISABLE_HTTP + if(0 == k->bodywrites && !is_empty_data) { + /* These checks are only made the first time we are about to + write a piece of the body */ + if(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)) { + /* HTTP-only checks */ + result = Curl_http_firstwrite(data, conn, done); + if(result || *done) + goto out; + } + } /* this is the first time we write a body part */ +#endif /* CURL_DISABLE_HTTP */ + +#ifndef CURL_DISABLE_HTTP + if(k->chunk) { + /* + * Here comes a chunked transfer flying and we need to decode this + * properly. While the name says read, this function both reads + * and writes away the data. + */ + CURLcode extra; + CHUNKcode res; + + consumed = 0; + res = Curl_httpchunk_read(data, buf, blen, &consumed, &extra); + + if(CHUNKE_OK < res) { + if(CHUNKE_PASSTHRU_ERROR == res) { + failf(data, "Failed reading the chunked-encoded stream"); + result = extra; + goto out; + } + failf(data, "%s in chunked-encoding", Curl_chunked_strerror(res)); + result = CURLE_RECV_ERROR; + goto out; + } + + buf += consumed; + blen -= consumed; + if(CHUNKE_STOP == res) { + /* we're done reading chunks! */ + k->keepon &= ~KEEP_RECV; /* read no more */ + /* chunks read successfully, download is complete */ + k->download_done = TRUE; + + /* N number of bytes at the end of the str buffer that weren't + written to the client. */ + if(conn->chunk.datasize) { + infof(data, "Leftovers after chunking: % " + CURL_FORMAT_CURL_OFF_T "u bytes", + conn->chunk.datasize); + } + } + /* If it returned OK, we just keep going */ + } +#endif /* CURL_DISABLE_HTTP */ + + max_recv -= blen; + + if(!k->chunk && (blen || k->badheader || is_empty_data)) { + /* If this is chunky transfer, it was already written */ + + if(k->badheader) { + /* we parsed a piece of data wrongly assuming it was a header + and now we output it as body instead */ + size_t headlen = Curl_dyn_len(&data->state.headerb); + + /* Don't let excess data pollute body writes */ + if(k->maxdownload != -1 && (curl_off_t)headlen > k->maxdownload) + headlen = (size_t)k->maxdownload; + + result = Curl_client_write(data, CLIENTWRITE_BODY, + Curl_dyn_ptr(&data->state.headerb), + headlen); + if(result) + goto out; + } + + if(blen) { +#ifndef CURL_DISABLE_POP3 + if(conn->handler->protocol & PROTO_FAMILY_POP3) { + result = k->ignorebody? CURLE_OK : + Curl_pop3_write(data, buf, blen); + } + else +#endif /* CURL_DISABLE_POP3 */ + result = Curl_client_write(data, CLIENTWRITE_BODY, buf, blen); + } + k->badheader = FALSE; /* taken care of now */ + + if(result) + goto out; + } + + if(k->download_done && !is_http3) { + /* HTTP/3 over QUIC should keep reading until QUIC connection + is closed. In contrast to HTTP/2 which can stop reading + from TCP connection, HTTP/3 over QUIC needs ACK from server + to ensure stream closure. It should keep reading. */ + k->keepon &= ~KEEP_RECV; /* we're done reading */ + } + } /* if(!header and data to read) */ + + if(is_empty_data) { + /* if we received nothing, the server closed the connection and we + are done */ + k->keepon &= ~KEEP_RECV; + k->download_done = TRUE; + } + + if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV)) { + /* this is a paused or stopped transfer */ + break; + } + + } while((max_recv > 0) && data_pending(data) && maxloops--); + + if(maxloops <= 0 || max_recv <= 0) { + /* we mark it as read-again-please */ + data->state.dselect_bits = CURL_CSELECT_IN; + *comeback = TRUE; + } + + if(((k->keepon & (KEEP_RECV|KEEP_SEND)) == KEEP_SEND) && + (conn->bits.close || data_eof_handled)) { + /* When we've read the entire thing and the close bit is set, the server + may now close the connection. If there's now any kind of sending going + on from our side, we need to stop that immediately. */ + infof(data, "we are done reading and this is set to close, stop send"); + k->keepon &= ~KEEP_SEND; /* no writing anymore either */ + k->keepon &= ~KEEP_SEND_PAUSE; /* no pausing anymore either */ + } + +out: + if(result) + DEBUGF(infof(data, "readwrite_data() -> %d", result)); + return result; +} + +CURLcode Curl_done_sending(struct Curl_easy *data, + struct SingleRequest *k) +{ + k->keepon &= ~KEEP_SEND; /* we're done writing */ + + /* These functions should be moved into the handler struct! */ + Curl_conn_ev_data_done_send(data); + + return CURLE_OK; +} + +#if defined(_WIN32) && defined(USE_WINSOCK) +#ifndef SIO_IDEAL_SEND_BACKLOG_QUERY +#define SIO_IDEAL_SEND_BACKLOG_QUERY 0x4004747B +#endif + +static void win_update_buffer_size(curl_socket_t sockfd) +{ + int result; + ULONG ideal; + DWORD ideallen; + result = WSAIoctl(sockfd, SIO_IDEAL_SEND_BACKLOG_QUERY, 0, 0, + &ideal, sizeof(ideal), &ideallen, 0, 0); + if(result == 0) { + setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, + (const char *)&ideal, sizeof(ideal)); + } +} +#else +#define win_update_buffer_size(x) +#endif + +#define curl_upload_refill_watermark(data) \ + ((ssize_t)((data)->set.upload_buffer_size >> 5)) + +/* + * Send data to upload to the server, when the socket is writable. + */ +static CURLcode readwrite_upload(struct Curl_easy *data, + struct connectdata *conn, + int *didwhat) +{ + ssize_t i, si; + ssize_t bytes_written; + CURLcode result; + ssize_t nread; /* number of bytes read */ + bool sending_http_headers = FALSE; + struct SingleRequest *k = &data->req; + + *didwhat |= KEEP_SEND; + + do { + curl_off_t nbody; + ssize_t offset = 0; + + if(0 != k->upload_present && + k->upload_present < curl_upload_refill_watermark(data) && + !k->upload_chunky &&/*(variable sized chunked header; append not safe)*/ + !k->upload_done && /*!(k->upload_done once k->upload_present sent)*/ + !(k->writebytecount + k->upload_present - k->pendingheader == + data->state.infilesize)) { + offset = k->upload_present; + } + + /* only read more data if there's no upload data already + present in the upload buffer, or if appending to upload buffer */ + if(0 == k->upload_present || offset) { + result = Curl_get_upload_buffer(data); + if(result) + return result; + if(offset && k->upload_fromhere != data->state.ulbuf) + memmove(data->state.ulbuf, k->upload_fromhere, offset); + /* init the "upload from here" pointer */ + k->upload_fromhere = data->state.ulbuf; + + if(!k->upload_done) { + /* HTTP pollution, this should be written nicer to become more + protocol agnostic. */ + size_t fillcount; + struct HTTP *http = k->p.http; + + if((k->exp100 == EXP100_SENDING_REQUEST) && + (http->sending == HTTPSEND_BODY)) { + /* If this call is to send body data, we must take some action: + We have sent off the full HTTP 1.1 request, and we shall now + go into the Expect: 100 state and await such a header */ + k->exp100 = EXP100_AWAITING_CONTINUE; /* wait for the header */ + k->keepon &= ~KEEP_SEND; /* disable writing */ + k->start100 = Curl_now(); /* timeout count starts now */ + *didwhat &= ~KEEP_SEND; /* we didn't write anything actually */ + /* set a timeout for the multi interface */ + Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); + break; + } + + if(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)) { + if(http->sending == HTTPSEND_REQUEST) + /* We're sending the HTTP request headers, not the data. + Remember that so we don't change the line endings. */ + sending_http_headers = TRUE; + else + sending_http_headers = FALSE; + } + + k->upload_fromhere += offset; + result = Curl_fillreadbuffer(data, data->set.upload_buffer_size-offset, + &fillcount); + k->upload_fromhere -= offset; + if(result) + return result; + + nread = offset + fillcount; + } + else + nread = 0; /* we're done uploading/reading */ + + if(!nread && (k->keepon & KEEP_SEND_PAUSE)) { + /* this is a paused transfer */ + break; + } + if(nread <= 0) { + result = Curl_done_sending(data, k); + if(result) + return result; + break; + } + + /* store number of bytes available for upload */ + k->upload_present = nread; + + /* convert LF to CRLF if so asked */ + if((!sending_http_headers) && ( +#ifdef CURL_DO_LINEEND_CONV + /* always convert if we're FTPing in ASCII mode */ + (data->state.prefer_ascii) || +#endif + (data->set.crlf))) { + /* Do we need to allocate a scratch buffer? */ + if(!data->state.scratch) { + data->state.scratch = malloc(2 * data->set.upload_buffer_size); + if(!data->state.scratch) { + failf(data, "Failed to alloc scratch buffer"); + + return CURLE_OUT_OF_MEMORY; + } + } + + /* + * ASCII/EBCDIC Note: This is presumably a text (not binary) + * transfer so the data should already be in ASCII. + * That means the hex values for ASCII CR (0x0d) & LF (0x0a) + * must be used instead of the escape sequences \r & \n. + */ + if(offset) + memcpy(data->state.scratch, k->upload_fromhere, offset); + for(i = offset, si = offset; i < nread; i++, si++) { + if(k->upload_fromhere[i] == 0x0a) { + data->state.scratch[si++] = 0x0d; + data->state.scratch[si] = 0x0a; + if(!data->set.crlf) { + /* we're here only because FTP is in ASCII mode... + bump infilesize for the LF we just added */ + if(data->state.infilesize != -1) + data->state.infilesize++; + } + } + else + data->state.scratch[si] = k->upload_fromhere[i]; + } + + if(si != nread) { + /* only perform the special operation if we really did replace + anything */ + nread = si; + + /* upload from the new (replaced) buffer instead */ + k->upload_fromhere = data->state.scratch; + + /* set the new amount too */ + k->upload_present = nread; + } + } + +#ifndef CURL_DISABLE_SMTP + if(conn->handler->protocol & PROTO_FAMILY_SMTP) { + result = Curl_smtp_escape_eob(data, nread, offset); + if(result) + return result; + } +#endif /* CURL_DISABLE_SMTP */ + } /* if 0 == k->upload_present or appended to upload buffer */ + else { + /* We have a partial buffer left from a previous "round". Use + that instead of reading more data */ + } + + /* write to socket (send away data) */ + result = Curl_write(data, + conn->writesockfd, /* socket to send to */ + k->upload_fromhere, /* buffer pointer */ + k->upload_present, /* buffer size */ + &bytes_written); /* actually sent */ + if(result) + return result; + +#if defined(_WIN32) && defined(USE_WINSOCK) + { + struct curltime n = Curl_now(); + if(Curl_timediff(n, k->last_sndbuf_update) > 1000) { + win_update_buffer_size(conn->writesockfd); + k->last_sndbuf_update = n; + } + } +#endif + + if(k->pendingheader) { + /* parts of what was sent was header */ + curl_off_t n = CURLMIN(k->pendingheader, bytes_written); + /* show the data before we change the pointer upload_fromhere */ + Curl_debug(data, CURLINFO_HEADER_OUT, k->upload_fromhere, (size_t)n); + k->pendingheader -= n; + nbody = bytes_written - n; /* size of the written body part */ + } + else + nbody = bytes_written; + + if(nbody) { + /* show the data before we change the pointer upload_fromhere */ + Curl_debug(data, CURLINFO_DATA_OUT, + &k->upload_fromhere[bytes_written - nbody], + (size_t)nbody); + + k->writebytecount += nbody; + Curl_pgrsSetUploadCounter(data, k->writebytecount); + } + + if((!k->upload_chunky || k->forbidchunk) && + (k->writebytecount == data->state.infilesize)) { + /* we have sent all data we were supposed to */ + k->upload_done = TRUE; + infof(data, "We are completely uploaded and fine"); + } + + if(k->upload_present != bytes_written) { + /* we only wrote a part of the buffer (if anything), deal with it! */ + + /* store the amount of bytes left in the buffer to write */ + k->upload_present -= bytes_written; + + /* advance the pointer where to find the buffer when the next send + is to happen */ + k->upload_fromhere += bytes_written; + } + else { + /* we've uploaded that buffer now */ + result = Curl_get_upload_buffer(data); + if(result) + return result; + k->upload_fromhere = data->state.ulbuf; + k->upload_present = 0; /* no more bytes left */ + + if(k->upload_done) { + result = Curl_done_sending(data, k); + if(result) + return result; + } + } + + + } while(0); /* just to break out from! */ + + return CURLE_OK; +} + +static int select_bits_paused(struct Curl_easy *data, int select_bits) +{ + /* See issue #11982: we really need to be careful not to progress + * a transfer direction when that direction is paused. Not all parts + * of our state machine are handling PAUSED transfers correctly. So, we + * do not want to go there. + * NOTE: we are only interested in PAUSE, not HOLD. */ + return (((select_bits & CURL_CSELECT_IN) && + (data->req.keepon & KEEP_RECV_PAUSE)) || + ((select_bits & CURL_CSELECT_OUT) && + (data->req.keepon & KEEP_SEND_PAUSE))); +} + +/* + * Curl_readwrite() is the low-level function to be called when data is to + * be read and written to/from the connection. + * + * return '*comeback' TRUE if we didn't properly drain the socket so this + * function should get called again without select() or similar in between! + */ +CURLcode Curl_readwrite(struct connectdata *conn, + struct Curl_easy *data, + bool *done, + bool *comeback) +{ + struct SingleRequest *k = &data->req; + CURLcode result; + struct curltime now; + int didwhat = 0; + int select_bits; + + if(data->state.dselect_bits) { + if(select_bits_paused(data, data->state.dselect_bits)) { + /* leave the bits unchanged, so they'll tell us what to do when + * this transfer gets unpaused. */ + DEBUGF(infof(data, "readwrite, dselect_bits, early return on PAUSED")); + result = CURLE_OK; + goto out; + } + select_bits = data->state.dselect_bits; + data->state.dselect_bits = 0; + } + else if(conn->cselect_bits) { + /* CAVEAT: adding `select_bits_paused()` check here makes test640 hang + * (among others). Which hints at strange state handling in FTP land... */ + select_bits = conn->cselect_bits; + conn->cselect_bits = 0; + } + else { + curl_socket_t fd_read; + curl_socket_t fd_write; + /* only use the proper socket if the *_HOLD bit is not set simultaneously + as then we are in rate limiting state in that transfer direction */ + if((k->keepon & KEEP_RECVBITS) == KEEP_RECV) + fd_read = conn->sockfd; + else + fd_read = CURL_SOCKET_BAD; + + if((k->keepon & KEEP_SENDBITS) == KEEP_SEND) + fd_write = conn->writesockfd; + else + fd_write = CURL_SOCKET_BAD; + + select_bits = Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, 0); + } + + if(select_bits == CURL_CSELECT_ERR) { + failf(data, "select/poll returned error"); + result = CURLE_SEND_ERROR; + goto out; + } + +#ifdef USE_HYPER + if(conn->datastream) { + result = conn->datastream(data, conn, &didwhat, done, select_bits); + if(result || *done) + goto out; + } + else { +#endif + /* We go ahead and do a read if we have a readable socket or if + the stream was rewound (in which case we have data in a + buffer) */ + if((k->keepon & KEEP_RECV) && (select_bits & CURL_CSELECT_IN)) { + result = readwrite_data(data, conn, k, &didwhat, done, comeback); + if(result || *done) + goto out; + } + + /* If we still have writing to do, we check if we have a writable socket. */ + if((k->keepon & KEEP_SEND) && (select_bits & CURL_CSELECT_OUT)) { + /* write */ + + result = readwrite_upload(data, conn, &didwhat); + if(result) + goto out; + } +#ifdef USE_HYPER + } +#endif + + now = Curl_now(); + if(!didwhat) { + /* no read no write, this is a timeout? */ + if(k->exp100 == EXP100_AWAITING_CONTINUE) { + /* This should allow some time for the header to arrive, but only a + very short time as otherwise it'll be too much wasted time too + often. */ + + /* Quoting RFC2616, section "8.2.3 Use of the 100 (Continue) Status": + + Therefore, when a client sends this header field to an origin server + (possibly via a proxy) from which it has never seen a 100 (Continue) + status, the client SHOULD NOT wait for an indefinite period before + sending the request body. + + */ + + timediff_t ms = Curl_timediff(now, k->start100); + if(ms >= data->set.expect_100_timeout) { + /* we've waited long enough, continue anyway */ + k->exp100 = EXP100_SEND_DATA; + k->keepon |= KEEP_SEND; + Curl_expire_done(data, EXPIRE_100_TIMEOUT); + infof(data, "Done waiting for 100-continue"); + } + } + + result = Curl_conn_ev_data_idle(data); + if(result) + goto out; + } + + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, now); + if(result) + goto out; + + if(k->keepon) { + if(0 > Curl_timeleft(data, &now, FALSE)) { + if(k->size != -1) { + failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %" + CURL_FORMAT_CURL_OFF_T " bytes received", + Curl_timediff(now, data->progress.t_startsingle), + k->bytecount, k->size); + } + else { + failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds with %" CURL_FORMAT_CURL_OFF_T " bytes received", + Curl_timediff(now, data->progress.t_startsingle), + k->bytecount); + } + result = CURLE_OPERATION_TIMEDOUT; + goto out; + } + } + else { + /* + * The transfer has been performed. Just make some general checks before + * returning. + */ + + if(!(data->req.no_body) && (k->size != -1) && + (k->bytecount != k->size) && +#ifdef CURL_DO_LINEEND_CONV + /* Most FTP servers don't adjust their file SIZE response for CRLFs, + so we'll check to see if the discrepancy can be explained + by the number of CRLFs we've changed to LFs. + */ + (k->bytecount != (k->size + data->state.crlf_conversions)) && +#endif /* CURL_DO_LINEEND_CONV */ + !k->newurl) { + failf(data, "transfer closed with %" CURL_FORMAT_CURL_OFF_T + " bytes remaining to read", k->size - k->bytecount); + result = CURLE_PARTIAL_FILE; + goto out; + } + if(!(data->req.no_body) && k->chunk && + (conn->chunk.state != CHUNK_STOP)) { + /* + * In chunked mode, return an error if the connection is closed prior to + * the empty (terminating) chunk is read. + * + * The condition above used to check for + * conn->proto.http->chunk.datasize != 0 which is true after reading + * *any* chunk, not just the empty chunk. + * + */ + failf(data, "transfer closed with outstanding read data remaining"); + result = CURLE_PARTIAL_FILE; + goto out; + } + if(Curl_pgrsUpdate(data)) { + result = CURLE_ABORTED_BY_CALLBACK; + goto out; + } + } + + /* Now update the "done" boolean we return */ + *done = (0 == (k->keepon&(KEEP_RECVBITS|KEEP_SENDBITS))) ? TRUE : FALSE; +out: + if(result) + DEBUGF(infof(data, "Curl_readwrite() -> %d", result)); + return result; +} + +/* + * Curl_single_getsock() gets called by the multi interface code when the app + * has requested to get the sockets for the current connection. This function + * will then be called once for every connection that the multi interface + * keeps track of. This function will only be called for connections that are + * in the proper state to have this information available. + */ +int Curl_single_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *sock) +{ + int bitmap = GETSOCK_BLANK; + unsigned sockindex = 0; + + if(conn->handler->perform_getsock) + return conn->handler->perform_getsock(data, conn, sock); + + /* don't include HOLD and PAUSE connections */ + if((data->req.keepon & KEEP_RECVBITS) == KEEP_RECV) { + + DEBUGASSERT(conn->sockfd != CURL_SOCKET_BAD); + + bitmap |= GETSOCK_READSOCK(sockindex); + sock[sockindex] = conn->sockfd; + } + + /* don't include HOLD and PAUSE connections */ + if((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) { + if((conn->sockfd != conn->writesockfd) || + bitmap == GETSOCK_BLANK) { + /* only if they are not the same socket and we have a readable + one, we increase index */ + if(bitmap != GETSOCK_BLANK) + sockindex++; /* increase index if we need two entries */ + + DEBUGASSERT(conn->writesockfd != CURL_SOCKET_BAD); + + sock[sockindex] = conn->writesockfd; + } + + bitmap |= GETSOCK_WRITESOCK(sockindex); + } + + return bitmap; +} + +/* Curl_init_CONNECT() gets called each time the handle switches to CONNECT + which means this gets called once for each subsequent redirect etc */ +void Curl_init_CONNECT(struct Curl_easy *data) +{ + data->state.fread_func = data->set.fread_func_set; + data->state.in = data->set.in_set; + data->state.upload = (data->state.httpreq == HTTPREQ_PUT); +} + +/* + * Curl_pretransfer() is called immediately before a transfer starts, and only + * once for one transfer no matter if it has redirects or do multi-pass + * authentication etc. + */ +CURLcode Curl_pretransfer(struct Curl_easy *data) +{ + CURLcode result; + + if(!data->state.url && !data->set.uh) { + /* we can't do anything without URL */ + failf(data, "No URL set"); + return CURLE_URL_MALFORMAT; + } + + /* since the URL may have been redirected in a previous use of this handle */ + if(data->state.url_alloc) { + /* the already set URL is allocated, free it first! */ + Curl_safefree(data->state.url); + data->state.url_alloc = FALSE; + } + + if(!data->state.url && data->set.uh) { + CURLUcode uc; + free(data->set.str[STRING_SET_URL]); + uc = curl_url_get(data->set.uh, + CURLUPART_URL, &data->set.str[STRING_SET_URL], 0); + if(uc) { + failf(data, "No URL set"); + return CURLE_URL_MALFORMAT; + } + } + + if(data->set.postfields && data->set.set_resume_from) { + /* we can't */ + failf(data, "cannot mix POSTFIELDS with RESUME_FROM"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + data->state.prefer_ascii = data->set.prefer_ascii; +#ifdef CURL_LIST_ONLY_PROTOCOL + data->state.list_only = data->set.list_only; +#endif + data->state.httpreq = data->set.method; + data->state.url = data->set.str[STRING_SET_URL]; + + /* Init the SSL session ID cache here. We do it here since we want to do it + after the *_setopt() calls (that could specify the size of the cache) but + before any transfer takes place. */ + result = Curl_ssl_initsessions(data, data->set.general_ssl.max_ssl_sessions); + if(result) + return result; + + data->state.requests = 0; + data->state.followlocation = 0; /* reset the location-follow counter */ + data->state.this_is_a_follow = FALSE; /* reset this */ + data->state.errorbuf = FALSE; /* no error has occurred */ + data->state.httpwant = data->set.httpwant; + data->state.httpversion = 0; + data->state.authproblem = FALSE; + data->state.authhost.want = data->set.httpauth; + data->state.authproxy.want = data->set.proxyauth; + Curl_safefree(data->info.wouldredirect); + Curl_data_priority_clear_state(data); + + if(data->state.httpreq == HTTPREQ_PUT) + data->state.infilesize = data->set.filesize; + else if((data->state.httpreq != HTTPREQ_GET) && + (data->state.httpreq != HTTPREQ_HEAD)) { + data->state.infilesize = data->set.postfieldsize; + if(data->set.postfields && (data->state.infilesize == -1)) + data->state.infilesize = (curl_off_t)strlen(data->set.postfields); + } + else + data->state.infilesize = 0; + + /* If there is a list of cookie files to read, do it now! */ + Curl_cookie_loadfiles(data); + + /* If there is a list of host pairs to deal with */ + if(data->state.resolve) + result = Curl_loadhostpairs(data); + + /* If there is a list of hsts files to read */ + Curl_hsts_loadfiles(data); + + if(!result) { + /* Allow data->set.use_port to set which port to use. This needs to be + * disabled for example when we follow Location: headers to URLs using + * different ports! */ + data->state.allow_port = TRUE; + +#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) + /************************************************************* + * Tell signal handler to ignore SIGPIPE + *************************************************************/ + if(!data->set.no_signal) + data->state.prev_signal = signal(SIGPIPE, SIG_IGN); +#endif + + Curl_initinfo(data); /* reset session-specific information "variables" */ + Curl_pgrsResetTransferSizes(data); + Curl_pgrsStartNow(data); + + /* 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 + consider to be fine */ + data->state.authhost.picked &= data->state.authhost.want; + data->state.authproxy.picked &= data->state.authproxy.want; + +#ifndef CURL_DISABLE_FTP + data->state.wildcardmatch = data->set.wildcard_enabled; + if(data->state.wildcardmatch) { + struct WildcardData *wc; + if(!data->wildcard) { + data->wildcard = calloc(1, sizeof(struct WildcardData)); + if(!data->wildcard) + return CURLE_OUT_OF_MEMORY; + } + wc = data->wildcard; + if(wc->state < CURLWC_INIT) { + if(wc->ftpwc) + wc->dtor(wc->ftpwc); + Curl_safefree(wc->pattern); + Curl_safefree(wc->path); + result = Curl_wildcard_init(wc); /* init wildcard structures */ + if(result) + return CURLE_OUT_OF_MEMORY; + } + } +#endif + result = Curl_hsts_loadcb(data, data->hsts); + } + + /* + * Set user-agent. Used for HTTP, but since we can attempt to tunnel + * basically anything through an HTTP proxy we can't limit this based on + * protocol. + */ + if(data->set.str[STRING_USERAGENT]) { + Curl_safefree(data->state.aptr.uagent); + data->state.aptr.uagent = + aprintf("User-Agent: %s\r\n", data->set.str[STRING_USERAGENT]); + if(!data->state.aptr.uagent) + return CURLE_OUT_OF_MEMORY; + } + + if(!result) + result = Curl_setstropt(&data->state.aptr.user, + data->set.str[STRING_USERNAME]); + if(!result) + result = Curl_setstropt(&data->state.aptr.passwd, + data->set.str[STRING_PASSWORD]); + if(!result) + result = Curl_setstropt(&data->state.aptr.proxyuser, + data->set.str[STRING_PROXYUSERNAME]); + if(!result) + result = Curl_setstropt(&data->state.aptr.proxypasswd, + data->set.str[STRING_PROXYPASSWORD]); + + data->req.headerbytecount = 0; + Curl_headers_cleanup(data); + return result; +} + +/* + * Curl_posttransfer() is called immediately after a transfer ends + */ +CURLcode Curl_posttransfer(struct Curl_easy *data) +{ +#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) + /* restore the signal handler for SIGPIPE before we get back */ + if(!data->set.no_signal) + signal(SIGPIPE, data->state.prev_signal); +#else + (void)data; /* unused parameter */ +#endif + + return CURLE_OK; +} + +/* + * Curl_follow() handles the URL redirect magic. Pass in the 'newurl' string + * as given by the remote server and set up the new URL to request. + * + * This function DOES NOT FREE the given url. + */ +CURLcode Curl_follow(struct Curl_easy *data, + char *newurl, /* the Location: string */ + followtype type) /* see transfer.h */ +{ +#ifdef CURL_DISABLE_HTTP + (void)data; + (void)newurl; + (void)type; + /* Location: following will not happen when HTTP is disabled */ + return CURLE_TOO_MANY_REDIRECTS; +#else + + /* Location: redirect */ + bool disallowport = FALSE; + bool reachedmax = FALSE; + CURLUcode uc; + + DEBUGASSERT(type != FOLLOW_NONE); + + if(type != FOLLOW_FAKE) + data->state.requests++; /* count all real follows */ + if(type == FOLLOW_REDIR) { + if((data->set.maxredirs != -1) && + (data->state.followlocation >= data->set.maxredirs)) { + reachedmax = TRUE; + type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected + to URL */ + } + else { + data->state.followlocation++; /* count redirect-followings, including + auth reloads */ + + if(data->set.http_auto_referer) { + CURLU *u; + char *referer = NULL; + + /* We are asked to automatically set the previous URL as the referer + when we get the next URL. We pick the ->url field, which may or may + not be 100% correct */ + + if(data->state.referer_alloc) { + Curl_safefree(data->state.referer); + data->state.referer_alloc = FALSE; + } + + /* Make a copy of the URL without credentials and fragment */ + u = curl_url(); + if(!u) + return CURLE_OUT_OF_MEMORY; + + uc = curl_url_set(u, CURLUPART_URL, data->state.url, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_USER, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0); + if(!uc) + uc = curl_url_get(u, CURLUPART_URL, &referer, 0); + + curl_url_cleanup(u); + + if(uc || !referer) + return CURLE_OUT_OF_MEMORY; + + data->state.referer = referer; + data->state.referer_alloc = TRUE; /* yes, free this later */ + } + } + } + + if((type != FOLLOW_RETRY) && + (data->req.httpcode != 401) && (data->req.httpcode != 407) && + Curl_is_absolute_url(newurl, NULL, 0, FALSE)) { + /* If this is not redirect due to a 401 or 407 response and an absolute + URL: don't allow a custom port number */ + disallowport = TRUE; + } + + DEBUGASSERT(data->state.uh); + uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, + (type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME : + ((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) | + CURLU_ALLOW_SPACE | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); + if(uc) { + if(type != FOLLOW_FAKE) { + failf(data, "The redirect target URL could not be parsed: %s", + curl_url_strerror(uc)); + return Curl_uc_to_curlcode(uc); + } + + /* the URL could not be parsed for some reason, but since this is FAKE + mode, just duplicate the field as-is */ + newurl = strdup(newurl); + if(!newurl) + return CURLE_OUT_OF_MEMORY; + } + else { + uc = curl_url_get(data->state.uh, CURLUPART_URL, &newurl, 0); + if(uc) + return Curl_uc_to_curlcode(uc); + + /* Clear auth if this redirects to a different port number or protocol, + unless permitted */ + if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) { + char *portnum; + int port; + bool clear = FALSE; + + if(data->set.use_port && data->state.allow_port) + /* a custom port is used */ + port = (int)data->set.use_port; + else { + uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum, + CURLU_DEFAULT_PORT); + if(uc) { + free(newurl); + return Curl_uc_to_curlcode(uc); + } + port = atoi(portnum); + free(portnum); + } + if(port != data->info.conn_remote_port) { + infof(data, "Clear auth, redirects to port from %u to %u", + data->info.conn_remote_port, port); + clear = TRUE; + } + else { + char *scheme; + const struct Curl_handler *p; + uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0); + if(uc) { + free(newurl); + return Curl_uc_to_curlcode(uc); + } + + p = Curl_get_scheme_handler(scheme); + if(p && (p->protocol != data->info.conn_protocol)) { + infof(data, "Clear auth, redirects scheme from %s to %s", + data->info.conn_scheme, scheme); + clear = TRUE; + } + free(scheme); + } + if(clear) { + Curl_safefree(data->state.aptr.user); + Curl_safefree(data->state.aptr.passwd); + } + } + } + + if(type == FOLLOW_FAKE) { + /* we're only figuring out the new url if we would've followed locations + but now we're done so we can get out! */ + data->info.wouldredirect = newurl; + + if(reachedmax) { + failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs); + return CURLE_TOO_MANY_REDIRECTS; + } + return CURLE_OK; + } + + if(disallowport) + data->state.allow_port = FALSE; + + if(data->state.url_alloc) + Curl_safefree(data->state.url); + + data->state.url = newurl; + data->state.url_alloc = TRUE; + + infof(data, "Issue another request to this URL: '%s'", data->state.url); + + /* + * We get here when the HTTP code is 300-399 (and 401). We need to perform + * differently based on exactly what return code there was. + * + * News from 7.10.6: we can also get here on a 401 or 407, in case we act on + * an HTTP (proxy-) authentication scheme other than Basic. + */ + switch(data->info.httpcode) { + /* 401 - Act on a WWW-Authenticate, we keep on moving and do the + Authorization: XXXX header in the HTTP request code snippet */ + /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the + Proxy-Authorization: XXXX header in the HTTP request code snippet */ + /* 300 - Multiple Choices */ + /* 306 - Not used */ + /* 307 - Temporary Redirect */ + default: /* for all above (and the unknown ones) */ + /* Some codes are explicitly mentioned since I've checked RFC2616 and they + * seem to be OK to POST to. + */ + break; + case 301: /* Moved Permanently */ + /* (quote from RFC7231, section 6.4.2) + * + * Note: For historical reasons, a user agent MAY change the request + * method from POST to GET for the subsequent request. If this + * behavior is undesired, the 307 (Temporary Redirect) status code + * can be used instead. + * + * ---- + * + * Many webservers expect this, so these servers often answers to a POST + * request with an error page. To be sure that libcurl gets the page that + * most user agents would get, libcurl has to force GET. + * + * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and + * can be overridden with CURLOPT_POSTREDIR. + */ + if((data->state.httpreq == HTTPREQ_POST + || data->state.httpreq == HTTPREQ_POST_FORM + || data->state.httpreq == HTTPREQ_POST_MIME) + && !(data->set.keep_post & CURL_REDIR_POST_301)) { + infof(data, "Switch from POST to GET"); + data->state.httpreq = HTTPREQ_GET; + } + break; + case 302: /* Found */ + /* (quote from RFC7231, section 6.4.3) + * + * Note: For historical reasons, a user agent MAY change the request + * method from POST to GET for the subsequent request. If this + * behavior is undesired, the 307 (Temporary Redirect) status code + * can be used instead. + * + * ---- + * + * Many webservers expect this, so these servers often answers to a POST + * request with an error page. To be sure that libcurl gets the page that + * most user agents would get, libcurl has to force GET. + * + * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and + * can be overridden with CURLOPT_POSTREDIR. + */ + if((data->state.httpreq == HTTPREQ_POST + || data->state.httpreq == HTTPREQ_POST_FORM + || data->state.httpreq == HTTPREQ_POST_MIME) + && !(data->set.keep_post & CURL_REDIR_POST_302)) { + infof(data, "Switch from POST to GET"); + data->state.httpreq = HTTPREQ_GET; + } + break; + + case 303: /* See Other */ + /* 'See Other' location is not the resource but a substitute for the + * resource. In this case we switch the method to GET/HEAD, unless the + * method is POST and the user specified to keep it as POST. + * https://github.com/curl/curl/issues/5237#issuecomment-614641049 + */ + if(data->state.httpreq != HTTPREQ_GET && + ((data->state.httpreq != HTTPREQ_POST && + data->state.httpreq != HTTPREQ_POST_FORM && + data->state.httpreq != HTTPREQ_POST_MIME) || + !(data->set.keep_post & CURL_REDIR_POST_303))) { + data->state.httpreq = HTTPREQ_GET; + infof(data, "Switch to %s", + data->req.no_body?"HEAD":"GET"); + } + break; + case 304: /* Not Modified */ + /* 304 means we did a conditional request and it was "Not modified". + * We shouldn't get any Location: header in this response! + */ + break; + case 305: /* Use Proxy */ + /* (quote from RFC2616, section 10.3.6): + * "The requested resource MUST be accessed through the proxy given + * by the Location field. The Location field gives the URI of the + * proxy. The recipient is expected to repeat this single request + * via the proxy. 305 responses MUST only be generated by origin + * servers." + */ + break; + } + Curl_pgrsTime(data, TIMER_REDIRECT); + Curl_pgrsResetTransferSizes(data); + + return CURLE_OK; +#endif /* CURL_DISABLE_HTTP */ +} + +/* Returns CURLE_OK *and* sets '*url' if a request retry is wanted. + + NOTE: that the *url is malloc()ed. */ +CURLcode Curl_retry_request(struct Curl_easy *data, char **url) +{ + struct connectdata *conn = data->conn; + bool retry = FALSE; + *url = NULL; + + /* if we're talking upload, we can't do the checks below, unless the protocol + is HTTP as when uploading over HTTP we will still get a response */ + if(data->state.upload && + !(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP))) + return CURLE_OK; + + if((data->req.bytecount + data->req.headerbytecount == 0) && + conn->bits.reuse && + (!data->req.no_body || (conn->handler->protocol & PROTO_FAMILY_HTTP)) +#ifndef CURL_DISABLE_RTSP + && (data->set.rtspreq != RTSPREQ_RECEIVE) +#endif + ) + /* We got no data, we attempted to reuse a connection. For HTTP this + can be a retry so we try again regardless if we expected a body. + For other protocols we only try again only if we expected a body. + + This might happen if the connection was left alive when we were + done using it before, but that was closed when we wanted to read from + it again. Bad luck. Retry the same request on a fresh connect! */ + retry = TRUE; + else if(data->state.refused_stream && + (data->req.bytecount + data->req.headerbytecount == 0) ) { + /* This was sent on a refused stream, safe to rerun. A refused stream + error can typically only happen on HTTP/2 level if the stream is safe + to issue again, but the nghttp2 API can deliver the message to other + streams as well, which is why this adds the check the data counters + too. */ + infof(data, "REFUSED_STREAM, retrying a fresh connect"); + data->state.refused_stream = FALSE; /* clear again */ + retry = TRUE; + } + if(retry) { +#define CONN_MAX_RETRIES 5 + if(data->state.retrycount++ >= CONN_MAX_RETRIES) { + failf(data, "Connection died, tried %d times before giving up", + CONN_MAX_RETRIES); + data->state.retrycount = 0; + return CURLE_SEND_ERROR; + } + infof(data, "Connection died, retrying a fresh connect (retry count: %d)", + data->state.retrycount); + *url = strdup(data->state.url); + if(!*url) + return CURLE_OUT_OF_MEMORY; + + connclose(conn, "retry"); /* close this connection */ + conn->bits.retry = TRUE; /* mark this as a connection we're about + to retry. Marking it this way should + prevent i.e HTTP transfers to return + error just because nothing has been + transferred! */ + + + if((conn->handler->protocol&PROTO_FAMILY_HTTP) && + data->req.writebytecount) { + data->state.rewindbeforesend = TRUE; + infof(data, "state.rewindbeforesend = TRUE"); + } + } + return CURLE_OK; +} + +/* + * Curl_setup_transfer() is called to setup some basic properties for the + * upcoming transfer. + */ +void +Curl_setup_transfer( + struct Curl_easy *data, /* transfer */ + int sockindex, /* socket index to read from or -1 */ + curl_off_t size, /* -1 if unknown at this point */ + bool getheader, /* TRUE if header parsing is wanted */ + int writesockindex /* socket index to write to, it may very well be + the same we read from. -1 disables */ + ) +{ + struct SingleRequest *k = &data->req; + struct connectdata *conn = data->conn; + struct HTTP *http = data->req.p.http; + bool httpsending; + + DEBUGASSERT(conn != NULL); + DEBUGASSERT((sockindex <= 1) && (sockindex >= -1)); + + httpsending = ((conn->handler->protocol&PROTO_FAMILY_HTTP) && + (http->sending == HTTPSEND_REQUEST)); + + if(conn->bits.multiplex || conn->httpversion >= 20 || httpsending) { + /* when multiplexing, the read/write sockets need to be the same! */ + conn->sockfd = sockindex == -1 ? + ((writesockindex == -1 ? CURL_SOCKET_BAD : conn->sock[writesockindex])) : + conn->sock[sockindex]; + conn->writesockfd = conn->sockfd; + if(httpsending) + /* special and very HTTP-specific */ + writesockindex = FIRSTSOCKET; + } + else { + conn->sockfd = sockindex == -1 ? + CURL_SOCKET_BAD : conn->sock[sockindex]; + conn->writesockfd = writesockindex == -1 ? + CURL_SOCKET_BAD:conn->sock[writesockindex]; + } + k->getheader = getheader; + + k->size = size; + + /* The code sequence below is placed in this function just because all + necessary input is not always known in do_complete() as this function may + be called after that */ + + if(!k->getheader) { + k->header = FALSE; + if(size > 0) + Curl_pgrsSetDownloadSize(data, size); + } + /* we want header and/or body, if neither then don't do this! */ + if(k->getheader || !data->req.no_body) { + + if(sockindex != -1) + k->keepon |= KEEP_RECV; + + if(writesockindex != -1) { + /* HTTP 1.1 magic: + + Even if we require a 100-return code before uploading data, we might + need to write data before that since the REQUEST may not have been + finished sent off just yet. + + Thus, we must check if the request has been sent before we set the + state info where we wait for the 100-return code + */ + if((data->state.expect100header) && + (conn->handler->protocol&PROTO_FAMILY_HTTP) && + (http->sending == HTTPSEND_BODY)) { + /* wait with write until we either got 100-continue or a timeout */ + k->exp100 = EXP100_AWAITING_CONTINUE; + k->start100 = Curl_now(); + + /* Set a timeout for the multi interface. Add the inaccuracy margin so + that we don't fire slightly too early and get denied to run. */ + Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); + } + else { + if(data->state.expect100header) + /* when we've sent off the rest of the headers, we must await a + 100-continue but first finish sending the request */ + k->exp100 = EXP100_SENDING_REQUEST; + + /* enable the write bit when we're not waiting for continue */ + k->keepon |= KEEP_SEND; + } + } /* if(writesockindex != -1) */ + } /* if(k->getheader || !data->req.no_body) */ + +} diff --git a/Utilities/cmcurl/lib/transfer.h b/Utilities/cmcurl/lib/transfer.h new file mode 100644 index 0000000..536ac24 --- /dev/null +++ b/Utilities/cmcurl/lib/transfer.h @@ -0,0 +1,73 @@ +#ifndef HEADER_CURL_TRANSFER_H +#define HEADER_CURL_TRANSFER_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#define Curl_headersep(x) ((((x)==':') || ((x)==';'))) +char *Curl_checkheaders(const struct Curl_easy *data, + const char *thisheader, + const size_t thislen); + +void Curl_init_CONNECT(struct Curl_easy *data); + +CURLcode Curl_pretransfer(struct Curl_easy *data); +CURLcode Curl_posttransfer(struct Curl_easy *data); + +typedef enum { + FOLLOW_NONE, /* not used within the function, just a placeholder to + allow initing to this */ + FOLLOW_FAKE, /* only records stuff, not actually following */ + FOLLOW_RETRY, /* set if this is a request retry as opposed to a real + redirect following */ + FOLLOW_REDIR /* a full true redirect */ +} followtype; + +CURLcode Curl_follow(struct Curl_easy *data, char *newurl, + followtype type); +CURLcode Curl_readwrite(struct connectdata *conn, + struct Curl_easy *data, bool *done, + bool *comeback); +int Curl_single_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); +CURLcode Curl_fillreadbuffer(struct Curl_easy *data, size_t bytes, + size_t *nreadp); +CURLcode Curl_retry_request(struct Curl_easy *data, char **url); +bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc); +CURLcode Curl_get_upload_buffer(struct Curl_easy *data); + +CURLcode Curl_done_sending(struct Curl_easy *data, + struct SingleRequest *k); + +/* This sets up a forthcoming transfer */ +void +Curl_setup_transfer (struct Curl_easy *data, + int sockindex, /* socket index to read from or -1 */ + curl_off_t size, /* -1 if unknown at this point */ + bool getheader, /* TRUE if header parsing is wanted */ + int writesockindex /* socket index to write to. May be + the same we read from. -1 + disables */ + ); + +#endif /* HEADER_CURL_TRANSFER_H */ diff --git a/Utilities/cmcurl/lib/url.c b/Utilities/cmcurl/lib/url.c new file mode 100644 index 0000000..b81785f --- /dev/null +++ b/Utilities/cmcurl/lib/url.c @@ -0,0 +1,4062 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif +#ifdef HAVE_IPHLPAPI_H +#include <Iphlpapi.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif + +#ifndef HAVE_SOCKET +#error "We can't compile without socket() support!" +#endif + +#include <limits.h> + +#include "doh.h" +#include "urldata.h" +#include "netrc.h" +#include "formdata.h" +#include "mime.h" +#include "vtls/vtls.h" +#include "hostip.h" +#include "transfer.h" +#include "sendf.h" +#include "progress.h" +#include "cookie.h" +#include "strcase.h" +#include "strerror.h" +#include "escape.h" +#include "strtok.h" +#include "share.h" +#include "content_encoding.h" +#include "http_digest.h" +#include "http_negotiate.h" +#include "select.h" +#include "multiif.h" +#include "easyif.h" +#include "speedcheck.h" +#include "warnless.h" +#include "getinfo.h" +#include "urlapi-int.h" +#include "system_win32.h" +#include "hsts.h" +#include "noproxy.h" +#include "cfilters.h" +#include "idn.h" + +/* And now for the protocols */ +#include "ftp.h" +#include "dict.h" +#include "telnet.h" +#include "tftp.h" +#include "http.h" +#include "http2.h" +#include "file.h" +#include "curl_ldap.h" +#include "vssh/ssh.h" +#include "imap.h" +#include "url.h" +#include "connect.h" +#include "inet_ntop.h" +#include "http_ntlm.h" +#include "curl_rtmp.h" +#include "gopher.h" +#include "mqtt.h" +#include "http_proxy.h" +#include "conncache.h" +#include "multihandle.h" +#include "strdup.h" +#include "setopt.h" +#include "altsvc.h" +#include "dynbuf.h" +#include "headers.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +#ifdef USE_NGHTTP2 +static void data_priority_cleanup(struct Curl_easy *data); +#else +#define data_priority_cleanup(x) +#endif + +/* Some parts of the code (e.g. chunked encoding) assume this buffer has at + * more than just a few bytes to play with. Don't let it become too small or + * bad things will happen. + */ +#if READBUFFER_SIZE < READBUFFER_MIN +# error READBUFFER_SIZE is too small +#endif + +#ifdef USE_UNIX_SOCKETS +#define UNIX_SOCKET_PREFIX "localhost" +#endif + +/* Reject URLs exceeding this length */ +#define MAX_URL_LEN 0xffff + +/* +* get_protocol_family() +* +* This is used to return the protocol family for a given protocol. +* +* Parameters: +* +* 'h' [in] - struct Curl_handler pointer. +* +* Returns the family as a single bit protocol identifier. +*/ +static curl_prot_t get_protocol_family(const struct Curl_handler *h) +{ + DEBUGASSERT(h); + DEBUGASSERT(h->family); + return h->family; +} + +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++) { + Curl_safefree(data->set.str[i]); + } + + for(j = (enum dupblob)0; j < BLOB_LAST; j++) { + Curl_safefree(data->set.blobs[j]); + } + + if(data->state.referer_alloc) { + Curl_safefree(data->state.referer); + data->state.referer_alloc = FALSE; + } + data->state.referer = NULL; + if(data->state.url_alloc) { + Curl_safefree(data->state.url); + data->state.url_alloc = FALSE; + } + data->state.url = NULL; + + Curl_mime_cleanpart(&data->set.mimepost); + +#ifndef CURL_DISABLE_COOKIES + curl_slist_free_all(data->state.cookielist); + data->state.cookielist = NULL; +#endif +} + +/* free the URL pieces */ +static void up_free(struct Curl_easy *data) +{ + struct urlpieces *up = &data->state.up; + Curl_safefree(up->scheme); + Curl_safefree(up->hostname); + Curl_safefree(up->port); + Curl_safefree(up->user); + Curl_safefree(up->password); + Curl_safefree(up->options); + Curl_safefree(up->path); + Curl_safefree(up->query); + curl_url_cleanup(data->state.uh); + data->state.uh = NULL; +} + +/* + * This is the internal function curl_easy_cleanup() calls. This should + * cleanup and free all resources associated with this sessionhandle. + * + * We ignore SIGPIPE when this is called from curl_easy_cleanup. + */ + +CURLcode Curl_close(struct Curl_easy **datap) +{ + struct Curl_easy *data; + + if(!datap || !*datap) + return CURLE_OK; + + data = *datap; + *datap = NULL; + + Curl_expire_clear(data); /* shut off timers */ + + /* Detach connection if any is left. This should not be normal, but can be + the case for example with CONNECT_ONLY + recv/send (test 556) */ + Curl_detach_connection(data); + if(!data->state.internal) { + if(data->multi) + /* This handle is still part of a multi handle, take care of this first + and detach this handle from there. */ + curl_multi_remove_handle(data->multi, data); + + if(data->multi_easy) { + /* when curl_easy_perform() is used, it creates its own multi handle to + use and this is the one */ + curl_multi_cleanup(data->multi_easy); + data->multi_easy = NULL; + } + } + + data->magic = 0; /* force a clear AFTER the possibly enforced removal from + the multi handle, since that function uses the magic + field! */ + + if(data->state.rangestringalloc) + free(data->state.range); + + /* freed here just in case DONE wasn't called */ + Curl_free_request_state(data); + + /* Close down all open SSL info and sessions */ + Curl_ssl_close_all(data); + Curl_safefree(data->state.first_host); + Curl_safefree(data->state.scratch); + Curl_ssl_free_certinfo(data); + + /* Cleanup possible redirect junk */ + free(data->req.newurl); + data->req.newurl = NULL; + + if(data->state.referer_alloc) { + Curl_safefree(data->state.referer); + data->state.referer_alloc = FALSE; + } + data->state.referer = NULL; + + up_free(data); + Curl_safefree(data->state.buffer); + Curl_dyn_free(&data->state.headerb); + Curl_safefree(data->state.ulbuf); + Curl_flush_cookies(data, TRUE); + Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]); + Curl_altsvc_cleanup(&data->asi); + Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]); +#ifndef CURL_DISABLE_HSTS + if(!data->share || !data->share->hsts) + Curl_hsts_cleanup(&data->hsts); + curl_slist_free_all(data->state.hstslist); /* clean up list */ +#endif +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) + Curl_http_auth_cleanup_digest(data); +#endif + Curl_safefree(data->info.contenttype); + Curl_safefree(data->info.wouldredirect); + + data_priority_cleanup(data); + + /* No longer a dirty share, if it exists */ + if(data->share) { + Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); + data->share->dirty--; + Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); + } + + Curl_safefree(data->state.aptr.proxyuserpwd); + Curl_safefree(data->state.aptr.uagent); + Curl_safefree(data->state.aptr.userpwd); + Curl_safefree(data->state.aptr.accept_encoding); + Curl_safefree(data->state.aptr.te); + Curl_safefree(data->state.aptr.rangeline); + Curl_safefree(data->state.aptr.ref); + Curl_safefree(data->state.aptr.host); + Curl_safefree(data->state.aptr.cookiehost); + Curl_safefree(data->state.aptr.rtsp_transport); + Curl_safefree(data->state.aptr.user); + Curl_safefree(data->state.aptr.passwd); + Curl_safefree(data->state.aptr.proxyuser); + Curl_safefree(data->state.aptr.proxypasswd); + +#ifndef CURL_DISABLE_DOH + if(data->req.doh) { + Curl_dyn_free(&data->req.doh->probe[0].serverdoh); + Curl_dyn_free(&data->req.doh->probe[1].serverdoh); + curl_slist_free_all(data->req.doh->headers); + Curl_safefree(data->req.doh); + } +#endif + +#ifndef CURL_DISABLE_HTTP + Curl_mime_cleanpart(data->state.formp); + Curl_safefree(data->state.formp); +#endif + + /* destruct wildcard structures if it is needed */ + Curl_wildcard_dtor(&data->wildcard); + Curl_freeset(data); + Curl_headers_cleanup(data); + free(data); + return CURLE_OK; +} + +/* + * Initialize the UserDefined fields within a Curl_easy. + * This may be safely called on a new or existing Curl_easy. + */ +CURLcode Curl_init_userdefined(struct Curl_easy *data) +{ + struct UserDefined *set = &data->set; + CURLcode result = CURLE_OK; + + set->out = stdout; /* default output to stdout */ + set->in_set = stdin; /* default input from stdin */ + set->err = stderr; /* default stderr to stderr */ + + /* use fwrite as default function to store output */ + set->fwrite_func = (curl_write_callback)fwrite; + + /* use fread as default function to read input */ + set->fread_func_set = (curl_read_callback)fread; + set->is_fread_set = 0; + + set->seek_func = ZERO_NULL; + set->seek_client = ZERO_NULL; + + set->filesize = -1; /* we don't know the size */ + set->postfieldsize = -1; /* unknown size */ + set->maxredirs = 30; /* sensible default */ + + set->method = HTTPREQ_GET; /* Default HTTP request */ +#ifndef CURL_DISABLE_RTSP + set->rtspreq = RTSPREQ_OPTIONS; /* Default RTSP request */ +#endif +#ifndef CURL_DISABLE_FTP + set->ftp_use_epsv = TRUE; /* FTP defaults to EPSV operations */ + set->ftp_use_eprt = TRUE; /* FTP defaults to EPRT operations */ + set->ftp_use_pret = FALSE; /* mainly useful for drftpd servers */ + set->ftp_filemethod = FTPFILE_MULTICWD; + set->ftp_skip_ip = TRUE; /* skip PASV IP by default */ +#endif + set->dns_cache_timeout = 60; /* Timeout every 60 seconds by default */ + + /* Set the default size of the SSL session ID cache */ + set->general_ssl.max_ssl_sessions = 5; + /* Timeout every 24 hours by default */ + set->general_ssl.ca_cache_timeout = 24 * 60 * 60; + + set->httpauth = CURLAUTH_BASIC; /* defaults to basic */ + +#ifndef CURL_DISABLE_PROXY + set->proxyport = 0; + set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */ + set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */ + /* SOCKS5 proxy auth defaults to username/password + GSS-API */ + set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI; +#endif + + /* make libcurl quiet by default: */ + set->hide_progress = TRUE; /* CURLOPT_NOPROGRESS changes these */ + + Curl_mime_initpart(&set->mimepost); + + Curl_ssl_easy_config_init(data); +#ifndef CURL_DISABLE_DOH + set->doh_verifyhost = TRUE; + set->doh_verifypeer = TRUE; +#endif +#ifdef USE_SSH + /* defaults to any auth type */ + set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; + set->new_directory_perms = 0755; /* Default permissions */ +#endif + + set->new_file_perms = 0644; /* Default permissions */ + set->allowed_protocols = (curl_prot_t) CURLPROTO_ALL; + set->redir_protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | + CURLPROTO_FTPS; + +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + /* + * disallow unprotected protection negotiation NEC reference implementation + * seem not to follow rfc1961 section 4.3/4.4 + */ + set->socks5_gssapi_nec = FALSE; +#endif + + /* Set the default CA cert bundle/path detected/specified at build time. + * + * If Schannel is the selected SSL backend then these locations are + * ignored. We allow setting CA location for schannel only when explicitly + * specified by the user via CURLOPT_CAINFO / --cacert. + */ + if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { +#if defined(CURL_CA_BUNDLE) + result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE); + if(result) + return result; + + result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY], + CURL_CA_BUNDLE); + if(result) + return result; +#endif +#if defined(CURL_CA_PATH) + result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH); + if(result) + return result; + + result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH); + if(result) + return result; +#endif + } + +#ifndef CURL_DISABLE_FTP + set->wildcard_enabled = FALSE; + set->chunk_bgn = ZERO_NULL; + set->chunk_end = ZERO_NULL; + set->fnmatch = ZERO_NULL; +#endif + set->tcp_keepalive = FALSE; + set->tcp_keepintvl = 60; + set->tcp_keepidle = 60; + set->tcp_fastopen = FALSE; + set->tcp_nodelay = TRUE; + set->ssl_enable_alpn = TRUE; + set->expect_100_timeout = 1000L; /* Wait for a second by default. */ + set->sep_headers = TRUE; /* separated header lists by default */ + set->buffer_size = READBUFFER_SIZE; + set->upload_buffer_size = UPLOADBUFFER_DEFAULT; + set->happy_eyeballs_timeout = CURL_HET_DEFAULT; + set->upkeep_interval_ms = CURL_UPKEEP_INTERVAL_DEFAULT; + set->maxconnects = DEFAULT_CONNCACHE_SIZE; /* for easy handles */ + set->maxage_conn = 118; + set->maxlifetime_conn = 0; + set->http09_allowed = FALSE; +#ifdef USE_HTTP2 + set->httpwant = CURL_HTTP_VERSION_2TLS +#else + set->httpwant = CURL_HTTP_VERSION_1_1 +#endif + ; +#if defined(USE_HTTP2) || defined(USE_HTTP3) + memset(&set->priority, 0, sizeof(set->priority)); +#endif + set->quick_exit = 0L; + return result; +} + +/** + * Curl_open() + * + * @param curl is a pointer to a sessionhandle pointer that gets set by this + * function. + * @return CURLcode + */ + +CURLcode Curl_open(struct Curl_easy **curl) +{ + CURLcode result; + struct Curl_easy *data; + + /* Very simple start-up: alloc the struct, init it with zeroes and return */ + data = calloc(1, sizeof(struct Curl_easy)); + if(!data) { + /* this is a very serious error */ + DEBUGF(fprintf(stderr, "Error: calloc of Curl_easy failed\n")); + return CURLE_OUT_OF_MEMORY; + } + + data->magic = CURLEASY_MAGIC_NUMBER; + + result = Curl_init_userdefined(data); + if(!result) { + Curl_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER); + Curl_initinfo(data); + + /* 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->progress.flags |= PGRS_HIDE; + data->state.current_speed = -1; /* init to negative == impossible */ + } + + if(result) { + Curl_dyn_free(&data->state.headerb); + Curl_freeset(data); + free(data); + data = NULL; + } + else + *curl = data; + + return result; +} + +static void conn_shutdown(struct Curl_easy *data) +{ + DEBUGASSERT(data); + infof(data, "Closing connection"); + + /* possible left-overs from the async name resolvers */ + Curl_resolver_cancel(data); + + Curl_conn_close(data, SECONDARYSOCKET); + Curl_conn_close(data, FIRSTSOCKET); +} + +static void conn_free(struct Curl_easy *data, struct connectdata *conn) +{ + size_t i; + + DEBUGASSERT(conn); + + for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) { + Curl_conn_cf_discard_all(data, conn, (int)i); + } + + Curl_resolver_cleanup(conn->resolve_async.resolver); + Curl_free_idnconverted_hostname(&conn->host); + Curl_free_idnconverted_hostname(&conn->conn_to_host); +#ifndef CURL_DISABLE_PROXY + Curl_free_idnconverted_hostname(&conn->http_proxy.host); + Curl_free_idnconverted_hostname(&conn->socks_proxy.host); + Curl_safefree(conn->http_proxy.user); + Curl_safefree(conn->socks_proxy.user); + Curl_safefree(conn->http_proxy.passwd); + Curl_safefree(conn->socks_proxy.passwd); + Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */ + Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */ +#endif + Curl_safefree(conn->user); + Curl_safefree(conn->passwd); + Curl_safefree(conn->sasl_authzid); + Curl_safefree(conn->options); + Curl_safefree(conn->oauth_bearer); +#ifndef CURL_DISABLE_HTTP + Curl_dyn_free(&conn->trailer); +#endif + Curl_safefree(conn->host.rawalloc); /* host name buffer */ + Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */ + Curl_safefree(conn->hostname_resolve); + Curl_safefree(conn->secondaryhostname); + Curl_safefree(conn->localdev); + Curl_ssl_conn_config_cleanup(conn); + +#ifdef USE_UNIX_SOCKETS + Curl_safefree(conn->unix_domain_socket); +#endif + + free(conn); /* free all the connection oriented data */ +} + +/* + * Disconnects the given connection. Note the connection may not be the + * primary connection, like when freeing room in the connection cache or + * killing of a dead old connection. + * + * A connection needs an easy handle when closing down. We support this passed + * in separately since the connection to get closed here is often already + * disassociated from an easy handle. + * + * This function MUST NOT reset state in the Curl_easy struct if that + * isn't strictly bound to the life-time of *this* particular connection. + * + */ + +void Curl_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection) +{ + /* there must be a connection to close */ + DEBUGASSERT(conn); + + /* it must be removed from the connection cache */ + DEBUGASSERT(!conn->bundle); + + /* there must be an associated transfer */ + DEBUGASSERT(data); + + /* the transfer must be detached from the connection */ + DEBUGASSERT(!data->conn); + + DEBUGF(infof(data, "Curl_disconnect(conn #%" + CURL_FORMAT_CURL_OFF_T ", dead=%d)", + conn->connection_id, dead_connection)); + /* + * If this connection isn't marked to force-close, leave it open if there + * are other users of it + */ + if(CONN_INUSE(conn) && !dead_connection) { + DEBUGF(infof(data, "Curl_disconnect when inuse: %zu", CONN_INUSE(conn))); + return; + } + + if(conn->dns_entry) { + Curl_resolv_unlock(data, conn->dns_entry); + conn->dns_entry = NULL; + } + + /* Cleanup NTLM connection-related data */ + Curl_http_auth_cleanup_ntlm(conn); + + /* Cleanup NEGOTIATE connection-related data */ + Curl_http_auth_cleanup_negotiate(conn); + + if(conn->connect_only) + /* treat the connection as dead in CONNECT_ONLY situations */ + dead_connection = TRUE; + + /* temporarily attach the connection to this transfer handle for the + disconnect and shutdown */ + Curl_attach_connection(data, conn); + + if(conn->handler && conn->handler->disconnect) + /* This is set if protocol-specific cleanups should be made */ + conn->handler->disconnect(data, conn, dead_connection); + + conn_shutdown(data); + Curl_resolver_cancel(data); + + /* detach it again */ + Curl_detach_connection(data); + + conn_free(data, conn); +} + +/* + * IsMultiplexingPossible() + * + * Return a bitmask with the available multiplexing options for the given + * requested connection. + */ +static int IsMultiplexingPossible(const struct Curl_easy *handle, + const struct connectdata *conn) +{ + int avail = 0; + + /* If an HTTP protocol and multiplexing is enabled */ + if((conn->handler->protocol & PROTO_FAMILY_HTTP) && + (!conn->bits.protoconnstart || !conn->bits.close)) { + + if(Curl_multiplex_wanted(handle->multi) && + (handle->state.httpwant >= CURL_HTTP_VERSION_2)) + /* allows HTTP/2 */ + avail |= CURLPIPE_MULTIPLEX; + } + return avail; +} + +#ifndef CURL_DISABLE_PROXY +static bool +proxy_info_matches(const struct proxy_info *data, + const struct proxy_info *needle) +{ + if((data->proxytype == needle->proxytype) && + (data->port == needle->port) && + strcasecompare(data->host.name, needle->host.name)) + return TRUE; + + return FALSE; +} + +static bool +socks_proxy_info_matches(const struct proxy_info *data, + const struct proxy_info *needle) +{ + if(!proxy_info_matches(data, needle)) + return FALSE; + + /* the user information is case-sensitive + or at least it is not defined as case-insensitive + see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1 */ + + /* curl_strequal does a case insensitive comparison, + so do not use it here! */ + if(Curl_timestrcmp(data->user, needle->user) || + Curl_timestrcmp(data->passwd, needle->passwd)) + return FALSE; + return TRUE; +} +#else +/* disabled, won't get called */ +#define proxy_info_matches(x,y) FALSE +#define socks_proxy_info_matches(x,y) FALSE +#endif + +/* A connection has to have been idle for a shorter time than 'maxage_conn' + (the success rate is just too low after this), or created less than + 'maxlifetime_conn' ago, to be subject for reuse. */ + +static bool conn_maxage(struct Curl_easy *data, + struct connectdata *conn, + struct curltime now) +{ + timediff_t idletime, lifetime; + + idletime = Curl_timediff(now, conn->lastused); + idletime /= 1000; /* integer seconds is fine */ + + if(idletime > data->set.maxage_conn) { + infof(data, "Too old connection (%" CURL_FORMAT_TIMEDIFF_T + " seconds idle), disconnect it", idletime); + return TRUE; + } + + lifetime = Curl_timediff(now, conn->created); + lifetime /= 1000; /* integer seconds is fine */ + + if(data->set.maxlifetime_conn && lifetime > data->set.maxlifetime_conn) { + infof(data, + "Too old connection (%" CURL_FORMAT_TIMEDIFF_T + " seconds since creation), disconnect it", lifetime); + return TRUE; + } + + + return FALSE; +} + +/* + * This function checks if the given connection is dead and extracts it from + * the connection cache if so. + * + * When this is called as a Curl_conncache_foreach() callback, the connection + * cache lock is held! + * + * Returns TRUE if the connection was dead and extracted. + */ +static bool extract_if_dead(struct connectdata *conn, + struct Curl_easy *data) +{ + if(!CONN_INUSE(conn)) { + /* The check for a dead socket makes sense only if the connection isn't in + use */ + bool dead; + struct curltime now = Curl_now(); + if(conn_maxage(data, conn, now)) { + /* avoid check if already too old */ + dead = TRUE; + } + else if(conn->handler->connection_check) { + /* The protocol has a special method for checking the state of the + connection. Use it to check if the connection is dead. */ + unsigned int state; + + /* briefly attach the connection to this transfer for the purpose of + checking it */ + Curl_attach_connection(data, conn); + + state = conn->handler->connection_check(data, conn, CONNCHECK_ISDEAD); + dead = (state & CONNRESULT_DEAD); + /* detach the connection again */ + Curl_detach_connection(data); + + } + else { + bool input_pending; + + 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. The 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. + */ + dead = TRUE; + } + Curl_detach_connection(data); + } + + if(dead) { + infof(data, "Connection %" CURL_FORMAT_CURL_OFF_T " seems to be dead", + conn->connection_id); + Curl_conncache_remove_conn(data, conn, FALSE); + return TRUE; + } + } + return FALSE; +} + +struct prunedead { + struct Curl_easy *data; + struct connectdata *extracted; +}; + +/* + * Wrapper to use extract_if_dead() function in Curl_conncache_foreach() + * + */ +static int call_extract_if_dead(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + struct prunedead *p = (struct prunedead *)param; + if(extract_if_dead(conn, data)) { + /* stop the iteration here, pass back the connection that was extracted */ + p->extracted = conn; + return 1; + } + return 0; /* continue iteration */ +} + +/* + * This function scans the connection cache 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. + */ +static void prune_dead_connections(struct Curl_easy *data) +{ + struct curltime now = Curl_now(); + timediff_t elapsed; + + DEBUGASSERT(!data->conn); /* no connection */ + CONNCACHE_LOCK(data); + elapsed = + Curl_timediff(now, data->state.conn_cache->last_cleanup); + CONNCACHE_UNLOCK(data); + + if(elapsed >= 1000L) { + struct prunedead prune; + prune.data = data; + prune.extracted = NULL; + while(Curl_conncache_foreach(data, data->state.conn_cache, &prune, + call_extract_if_dead)) { + /* unlocked */ + + /* remove connection from cache */ + Curl_conncache_remove_conn(data, prune.extracted, TRUE); + + /* disconnect it */ + Curl_disconnect(data, prune.extracted, TRUE); + } + CONNCACHE_LOCK(data); + data->state.conn_cache->last_cleanup = now; + CONNCACHE_UNLOCK(data); + } +} + +#ifdef USE_SSH +static bool ssh_config_matches(struct connectdata *one, + struct connectdata *two) +{ + return (Curl_safecmp(one->proto.sshc.rsa, two->proto.sshc.rsa) && + Curl_safecmp(one->proto.sshc.rsa_pub, two->proto.sshc.rsa_pub)); +} +#else +#define ssh_config_matches(x,y) FALSE +#endif + +/* + * Given one filled in connection struct (named needle), this function should + * detect if there already is one that has all the significant details + * exactly the same and thus should be used instead. + * + * If there is a match, this function returns TRUE - and has marked the + * connection as 'in-use'. It must later be called with ConnectionDone() to + * return back to 'idle' (unused) state. + * + * The force_reuse flag is set if the connection must be used. + */ +static bool +ConnectionExists(struct Curl_easy *data, + struct connectdata *needle, + struct connectdata **usethis, + bool *force_reuse, + bool *waitpipe) +{ + struct connectdata *chosen = NULL; + bool foundPendingCandidate = FALSE; + bool canmultiplex = FALSE; + struct connectbundle *bundle; + struct Curl_llist_element *curr; + +#ifdef USE_NTLM + bool wantNTLMhttp = ((data->state.authhost.want & + (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + (needle->handler->protocol & PROTO_FAMILY_HTTP)); +#ifndef CURL_DISABLE_PROXY + bool wantProxyNTLMhttp = (needle->bits.proxy_user_passwd && + ((data->state.authproxy.want & + (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + (needle->handler->protocol & PROTO_FAMILY_HTTP))); +#else + bool wantProxyNTLMhttp = FALSE; +#endif +#endif + /* plain HTTP with upgrade */ + bool h2upgrade = (data->state.httpwant == CURL_HTTP_VERSION_2_0) && + (needle->handler->protocol & CURLPROTO_HTTP); + + *usethis = NULL; + *force_reuse = FALSE; + *waitpipe = FALSE; + + /* Look up the bundle with all the connections to this particular host. + Locks the connection cache, beware of early returns! */ + bundle = Curl_conncache_find_bundle(data, needle, data->state.conn_cache); + if(!bundle) { + CONNCACHE_UNLOCK(data); + return FALSE; + } + infof(data, "Found bundle for host: %p [%s]", + (void *)bundle, (bundle->multiuse == BUNDLE_MULTIPLEX ? + "can multiplex" : "serially")); + + /* We can only multiplex iff the transfer allows it AND we know + * that the server we want to talk to supports it as well. */ + canmultiplex = FALSE; + if(IsMultiplexingPossible(data, needle)) { + if(bundle->multiuse == BUNDLE_UNKNOWN) { + if(data->set.pipewait) { + infof(data, "Server doesn't support multiplex yet, wait"); + *waitpipe = TRUE; + CONNCACHE_UNLOCK(data); + return FALSE; /* no reuse */ + } + infof(data, "Server doesn't support multiplex (yet)"); + } + else if(bundle->multiuse == BUNDLE_MULTIPLEX) { + if(Curl_multiplex_wanted(data->multi)) + canmultiplex = TRUE; + else + infof(data, "Could multiplex, but not asked to"); + } + else if(bundle->multiuse == BUNDLE_NO_MULTIUSE) { + infof(data, "Can not multiplex, even if we wanted to"); + } + } + + curr = bundle->conn_list.head; + while(curr) { + struct connectdata *check = curr->ptr; + /* Get next node now. We might remove a dead `check` connection which + * would invalidate `curr` as well. */ + curr = curr->next; + + /* Note that if we use an HTTP proxy in normal mode (no tunneling), we + * check connections to that proxy and not to the actual remote server. + */ + if(check->connect_only || check->bits.close) + /* connect-only or to-be-closed connections will not be reused */ + continue; + + if(data->set.ipver != CURL_IPRESOLVE_WHATEVER + && data->set.ipver != check->ip_version) { + /* skip because the connection is not via the requested IP version */ + continue; + } + + if(!canmultiplex) { + if(Curl_resolver_asynch() && + /* primary_ip[0] is NUL only if the resolving of the name hasn't + completed yet and until then we don't reuse this connection */ + !check->primary_ip[0]) + continue; + } + + if(CONN_INUSE(check)) { + if(!canmultiplex) { + /* transfer can't be multiplexed and check is in use */ + continue; + } + else { + /* Could multiplex, but not when check belongs to another multi */ + struct Curl_llist_element *e = check->easyq.head; + struct Curl_easy *entry = e->ptr; + if(entry->multi != data->multi) + continue; + } + } + + if(!Curl_conn_is_connected(check, FIRSTSOCKET)) { + foundPendingCandidate = TRUE; + /* Don't pick a connection that hasn't connected yet */ + infof(data, "Connection #%" CURL_FORMAT_CURL_OFF_T + " isn't open enough, can't reuse", check->connection_id); + continue; + } + + /* `check` is connected. if it is in use and does not support multiplex, + * we cannot use it. */ + if(!check->bits.multiplex && CONN_INUSE(check)) + continue; + +#ifdef USE_UNIX_SOCKETS + if(needle->unix_domain_socket) { + if(!check->unix_domain_socket) + continue; + if(strcmp(needle->unix_domain_socket, check->unix_domain_socket)) + continue; + if(needle->bits.abstract_unix_socket != + check->bits.abstract_unix_socket) + continue; + } + else if(check->unix_domain_socket) + continue; +#endif + + if((needle->handler->flags&PROTOPT_SSL) != + (check->handler->flags&PROTOPT_SSL)) + /* don't do mixed SSL and non-SSL connections */ + if(get_protocol_family(check->handler) != + needle->handler->protocol || !check->bits.tls_upgraded) + /* except protocols that have been upgraded via TLS */ + continue; + + if(needle->bits.conn_to_host != check->bits.conn_to_host) + /* don't mix connections that use the "connect to host" feature and + * connections that don't use this feature */ + continue; + + if(needle->bits.conn_to_port != check->bits.conn_to_port) + /* don't mix connections that use the "connect to port" feature and + * connections that don't use this feature */ + continue; + +#ifndef CURL_DISABLE_PROXY + if(needle->bits.httpproxy != check->bits.httpproxy || + needle->bits.socksproxy != check->bits.socksproxy) + continue; + + if(needle->bits.socksproxy && + !socks_proxy_info_matches(&needle->socks_proxy, + &check->socks_proxy)) + continue; + + if(needle->bits.httpproxy) { + if(needle->bits.tunnel_proxy != check->bits.tunnel_proxy) + continue; + + if(!proxy_info_matches(&needle->http_proxy, &check->http_proxy)) + continue; + + if(IS_HTTPS_PROXY(needle->http_proxy.proxytype)) { + /* https proxies come in different types, http/1.1, h2, ... */ + if(needle->http_proxy.proxytype != check->http_proxy.proxytype) + continue; + /* match SSL config to proxy */ + if(!Curl_ssl_conn_config_match(data, check, TRUE)) { + DEBUGF(infof(data, + "Connection #%" CURL_FORMAT_CURL_OFF_T + " has different SSL proxy parameters, can't reuse", + check->connection_id)); + continue; + } + /* the SSL config to the server, which may apply here is checked + * further below */ + } + } +#endif + + if(h2upgrade && !check->httpversion && canmultiplex) { + if(data->set.pipewait) { + infof(data, "Server upgrade doesn't support multiplex yet, wait"); + *waitpipe = TRUE; + CONNCACHE_UNLOCK(data); + return FALSE; /* no reuse */ + } + infof(data, "Server upgrade cannot be used"); + continue; /* can't be used atm */ + } + + if(needle->localdev || needle->localport) { + /* If we are bound to a specific local end (IP+port), we must not + reuse a random other one, although if we didn't ask for a + particular one we can reuse one that was bound. + + This comparison is a bit rough and too strict. Since the input + parameters can be specified in numerous ways and still end up the + same 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 shouldn't hurt anyone very much. + */ + if((check->localport != needle->localport) || + (check->localportrange != needle->localportrange) || + (needle->localdev && + (!check->localdev || strcmp(check->localdev, needle->localdev)))) + continue; + } + + if(!(needle->handler->flags & PROTOPT_CREDSPERREQUEST)) { + /* This protocol requires credentials per connection, + so verify that we're using the same name and password as well */ + if(Curl_timestrcmp(needle->user, check->user) || + Curl_timestrcmp(needle->passwd, check->passwd) || + Curl_timestrcmp(needle->sasl_authzid, check->sasl_authzid) || + Curl_timestrcmp(needle->oauth_bearer, check->oauth_bearer)) { + /* one of them was different */ + continue; + } + } + + /* GSS delegation differences do not actually affect every connection + and auth method, but this check takes precaution before efficiency */ + if(needle->gssapi_delegation != check->gssapi_delegation) + continue; + + /* If looking for HTTP and the HTTP version we want is less + * than the HTTP version of the check connection, continue looking */ + if((needle->handler->protocol & PROTO_FAMILY_HTTP) && + (((check->httpversion >= 20) && + (data->state.httpwant < CURL_HTTP_VERSION_2_0)) + || ((check->httpversion >= 30) && + (data->state.httpwant < CURL_HTTP_VERSION_3)))) + continue; +#ifdef USE_SSH + else if(get_protocol_family(needle->handler) & PROTO_FAMILY_SSH) { + if(!ssh_config_matches(needle, check)) + continue; + } +#endif +#ifndef CURL_DISABLE_FTP + else if(get_protocol_family(needle->handler) & PROTO_FAMILY_FTP) { + /* Also match ACCOUNT, ALTERNATIVE-TO-USER, USE_SSL and CCC options */ + if(Curl_timestrcmp(needle->proto.ftpc.account, + check->proto.ftpc.account) || + Curl_timestrcmp(needle->proto.ftpc.alternative_to_user, + check->proto.ftpc.alternative_to_user) || + (needle->proto.ftpc.use_ssl != check->proto.ftpc.use_ssl) || + (needle->proto.ftpc.ccc != check->proto.ftpc.ccc)) + continue; + } +#endif + + /* Additional match requirements if talking TLS OR + * not talking to a HTTP proxy OR using a tunnel through a proxy */ + if((needle->handler->flags&PROTOPT_SSL) +#ifndef CURL_DISABLE_PROXY + || !needle->bits.httpproxy || needle->bits.tunnel_proxy +#endif + ) { + /* Talking the same protocol scheme or a TLS upgraded protocol in the + * same protocol family? */ + if(!strcasecompare(needle->handler->scheme, check->handler->scheme) && + (get_protocol_family(check->handler) != + needle->handler->protocol || !check->bits.tls_upgraded)) + continue; + + /* If needle has "conn_to_*" set, check must match this */ + if((needle->bits.conn_to_host && !strcasecompare( + needle->conn_to_host.name, check->conn_to_host.name)) || + (needle->bits.conn_to_port && + needle->conn_to_port != check->conn_to_port)) + continue; + + /* hostname and port must match */ + if(!strcasecompare(needle->host.name, check->host.name) || + needle->remote_port != check->remote_port) + continue; + + /* If talking TLS, check needs to use the same SSL options. */ + if((needle->handler->flags & PROTOPT_SSL) && + !Curl_ssl_conn_config_match(data, check, FALSE)) { + DEBUGF(infof(data, + "Connection #%" CURL_FORMAT_CURL_OFF_T + " has different SSL parameters, can't reuse", + check->connection_id)); + continue; + } + } + +#if defined(USE_NTLM) + /* 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(wantNTLMhttp) { + if(Curl_timestrcmp(needle->user, check->user) || + Curl_timestrcmp(needle->passwd, check->passwd)) { + + /* we prefer a credential match, but this is at least a connection + that can be reused and "upgraded" to NTLM */ + if(check->http_ntlm_state == NTLMSTATE_NONE) + chosen = check; + continue; + } + } + else if(check->http_ntlm_state != NTLMSTATE_NONE) { + /* Connection is using NTLM auth but we don't want NTLM */ + continue; + } + +#ifndef CURL_DISABLE_PROXY + /* Same for Proxy NTLM authentication */ + if(wantProxyNTLMhttp) { + /* Both check->http_proxy.user and check->http_proxy.passwd can be + * NULL */ + if(!check->http_proxy.user || !check->http_proxy.passwd) + continue; + + if(Curl_timestrcmp(needle->http_proxy.user, + check->http_proxy.user) || + Curl_timestrcmp(needle->http_proxy.passwd, + check->http_proxy.passwd)) + continue; + } + else if(check->proxy_ntlm_state != NTLMSTATE_NONE) { + /* Proxy connection is using NTLM auth but we don't want NTLM */ + continue; + } +#endif + if(wantNTLMhttp || wantProxyNTLMhttp) { + /* Credentials are already checked, we may use this connection. + * With NTLM being weird as it is, we MUST use a + * connection where it has already been fully negotiated. + * If it has not, we keep on looking for a better one. */ + chosen = check; + + if((wantNTLMhttp && + (check->http_ntlm_state != NTLMSTATE_NONE)) || + (wantProxyNTLMhttp && + (check->proxy_ntlm_state != NTLMSTATE_NONE))) { + /* We must use this connection, no other */ + *force_reuse = TRUE; + break; + } + /* Continue look up for a better connection */ + continue; + } +#endif + + if(CONN_INUSE(check)) { + DEBUGASSERT(canmultiplex); + DEBUGASSERT(check->bits.multiplex); + /* If multiplexed, make sure we don't go over concurrency limit */ + if(CONN_INUSE(check) >= + Curl_multi_max_concurrent_streams(data->multi)) { + infof(data, "client side MAX_CONCURRENT_STREAMS reached" + ", skip (%zu)", CONN_INUSE(check)); + continue; + } + if(CONN_INUSE(check) >= + Curl_conn_get_max_concurrent(data, check, FIRSTSOCKET)) { + infof(data, "MAX_CONCURRENT_STREAMS reached, skip (%zu)", + CONN_INUSE(check)); + continue; + } + /* When not multiplexed, we have a match here! */ + infof(data, "Multiplexed connection found"); + } + else if(extract_if_dead(check, data)) { + /* disconnect it */ + Curl_disconnect(data, check, TRUE); + continue; + } + + /* We have found a connection. Let's stop searching. */ + chosen = check; + break; + } /* loop over connection bundle */ + + if(chosen) { + /* mark it as used before releasing the lock */ + Curl_attach_connection(data, chosen); + CONNCACHE_UNLOCK(data); + *usethis = chosen; + return TRUE; /* yes, we found one to use! */ + } + CONNCACHE_UNLOCK(data); + + if(foundPendingCandidate && data->set.pipewait) { + infof(data, + "Found pending candidate for reuse and CURLOPT_PIPEWAIT is set"); + *waitpipe = TRUE; + } + + return FALSE; /* no matching connecting exists */ +} + +/* + * verboseconnect() displays verbose information after a connect + */ +#ifndef CURL_DISABLE_VERBOSE_STRINGS +void Curl_verboseconnect(struct Curl_easy *data, + struct connectdata *conn) +{ + if(data->set.verbose) + infof(data, "Connected to %s (%s) port %u", + CURL_CONN_HOST_DISPNAME(conn), conn->primary_ip, conn->port); +} +#endif + +/* + * Allocate and initialize a new connectdata object. + */ +static struct connectdata *allocate_conn(struct Curl_easy *data) +{ + struct connectdata *conn = calloc(1, sizeof(struct connectdata)); + if(!conn) + return NULL; + + /* and we setup a few fields in case we end up actually using this struct */ + + conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ + conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ + conn->connection_id = -1; /* no ID */ + conn->port = -1; /* unknown at this point */ + conn->remote_port = -1; /* unknown at this point */ + + /* Default protocol-independent behavior doesn't support persistent + connections, so we set this to force-close. Protocols that support + this need to set this to FALSE in their "curl_do" functions. */ + connclose(conn, "Default to force-close"); + + /* Store creation time to help future close decision making */ + conn->created = Curl_now(); + + /* Store current time to give a baseline to keepalive connection times. */ + conn->keepalive = conn->created; + +#ifndef CURL_DISABLE_PROXY + conn->http_proxy.proxytype = data->set.proxytype; + conn->socks_proxy.proxytype = CURLPROXY_SOCKS4; + + /* note that these two proxy bits are now just on what looks to be + requested, they may be altered down the road */ + conn->bits.proxy = (data->set.str[STRING_PROXY] && + *data->set.str[STRING_PROXY]) ? TRUE : FALSE; + conn->bits.httpproxy = (conn->bits.proxy && + (conn->http_proxy.proxytype == CURLPROXY_HTTP || + conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0 || + IS_HTTPS_PROXY(conn->http_proxy.proxytype))) ? + TRUE : FALSE; + conn->bits.socksproxy = (conn->bits.proxy && + !conn->bits.httpproxy) ? TRUE : FALSE; + + if(data->set.str[STRING_PRE_PROXY] && *data->set.str[STRING_PRE_PROXY]) { + conn->bits.proxy = TRUE; + conn->bits.socksproxy = TRUE; + } + + conn->bits.proxy_user_passwd = + (data->state.aptr.proxyuser) ? TRUE : FALSE; + conn->bits.tunnel_proxy = data->set.tunnel_thru_httpproxy; +#endif /* CURL_DISABLE_PROXY */ + +#ifndef CURL_DISABLE_FTP + conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; + conn->bits.ftp_use_eprt = data->set.ftp_use_eprt; +#endif + conn->ip_version = data->set.ipver; + conn->connect_only = data->set.connect_only; + conn->transport = TRNSPRT_TCP; /* most of them are TCP streams */ + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ + defined(NTLM_WB_ENABLED) + conn->ntlm.ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; + conn->proxyntlm.ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; +#endif + + /* Initialize the easy handle list */ + Curl_llist_init(&conn->easyq, NULL); + +#ifdef HAVE_GSSAPI + conn->data_prot = PROT_CLEAR; +#endif + + /* Store the local bind parameters that will be used for this connection */ + if(data->set.str[STRING_DEVICE]) { + conn->localdev = strdup(data->set.str[STRING_DEVICE]); + if(!conn->localdev) + goto error; + } +#ifndef CURL_DISABLE_BINDLOCAL + conn->localportrange = data->set.localportrange; + conn->localport = data->set.localport; +#endif + + /* the close socket stuff needs to be copied to the connection struct as + 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; + conn->gssapi_delegation = data->set.gssapi_delegation; + + return conn; +error: + + free(conn->localdev); + free(conn); + return NULL; +} + +const struct Curl_handler *Curl_get_scheme_handler(const char *scheme) +{ + return Curl_getn_scheme_handler(scheme, strlen(scheme)); +} + +/* returns the handler if the given scheme is built-in */ +const struct Curl_handler *Curl_getn_scheme_handler(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 + 7. if needed, adjust the #ifdefs in schemetable.c and rerun + */ + static const struct Curl_handler * const protocols[67] = { +#ifndef CURL_DISABLE_FILE + &Curl_handler_file, +#else + NULL, +#endif + NULL, NULL, +#if defined(USE_SSL) && !defined(CURL_DISABLE_GOPHER) + &Curl_handler_gophers, +#else + NULL, +#endif + NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmpe, +#else + NULL, +#endif +#ifndef CURL_DISABLE_SMTP + &Curl_handler_smtp, +#else + NULL, +#endif +#if defined(USE_SSH) + &Curl_handler_sftp, +#else + NULL, +#endif +#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) && \ + (SIZEOF_CURL_OFF_T > 4) + &Curl_handler_smb, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_SMTP) + &Curl_handler_smtps, +#else + NULL, +#endif +#ifndef CURL_DISABLE_TELNET + &Curl_handler_telnet, +#else + NULL, +#endif +#ifndef CURL_DISABLE_GOPHER + &Curl_handler_gopher, +#else + NULL, +#endif +#ifndef CURL_DISABLE_TFTP + &Curl_handler_tftp, +#else + NULL, +#endif + NULL, NULL, NULL, +#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) + &Curl_handler_ftps, +#else + NULL, +#endif +#ifndef CURL_DISABLE_HTTP + &Curl_handler_http, +#else + NULL, +#endif +#ifndef CURL_DISABLE_IMAP + &Curl_handler_imap, +#else + NULL, +#endif +#ifdef USE_LIBRTMP + &Curl_handler_rtmps, +#else + NULL, +#endif +#ifdef USE_LIBRTMP + &Curl_handler_rtmpt, +#else + NULL, +#endif + NULL, NULL, NULL, +#if !defined(CURL_DISABLE_LDAP) && \ + !defined(CURL_DISABLE_LDAPS) && \ + ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ + (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) + &Curl_handler_ldaps, +#else + NULL, +#endif +#if defined(USE_WEBSOCKETS) && \ + defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) + &Curl_handler_wss, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) + &Curl_handler_https, +#else + NULL, +#endif + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +#ifndef CURL_DISABLE_RTSP + &Curl_handler_rtsp, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_SMB) && \ + defined(USE_CURL_NTLM_CORE) && (SIZEOF_CURL_OFF_T > 4) + &Curl_handler_smbs, +#else + NULL, +#endif +#if defined(USE_SSH) && !defined(USE_WOLFSSH) + &Curl_handler_scp, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifndef CURL_DISABLE_POP3 + &Curl_handler_pop3, +#else + NULL, +#endif + NULL, NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmp, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmpte, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifndef CURL_DISABLE_DICT + &Curl_handler_dict, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifndef CURL_DISABLE_MQTT + &Curl_handler_mqtt, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_POP3) + &Curl_handler_pop3s, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_IMAP) + &Curl_handler_imaps, +#else + NULL, +#endif + NULL, +#if defined(USE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) + &Curl_handler_ws, +#else + NULL, +#endif + NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmpts, +#else + NULL, +#endif +#ifndef CURL_DISABLE_LDAP + &Curl_handler_ldap, +#else + NULL, +#endif + NULL, NULL, +#ifndef CURL_DISABLE_FTP + &Curl_handler_ftp, +#else + NULL, +#endif + }; + + if(len && (len <= 7)) { + const char *s = scheme; + size_t l = len; + const struct Curl_handler *h; + unsigned int c = 978; + while(l) { + c <<= 5; + c += Curl_raw_tolower(*s); + s++; + l--; + } + + h = protocols[c % 67]; + if(h && strncasecompare(scheme, h->scheme, len) && !h->scheme[len]) + return h; + } + return NULL; +} + +static CURLcode findprotocol(struct Curl_easy *data, + struct connectdata *conn, + const char *protostr) +{ + const struct Curl_handler *p = Curl_get_scheme_handler(protostr); + + if(p && /* Protocol found in table. Check if allowed */ + (data->set.allowed_protocols & p->protocol)) { + + /* it is allowed for "normal" request, now do an extra check if this is + the result of a redirect */ + if(data->state.this_is_a_follow && + !(data->set.redir_protocols & p->protocol)) + /* nope, get out */ + ; + else { + /* Perform setup complement if some. */ + conn->handler = conn->given = p; + /* 'port' and 'remote_port' are set in setup_connection_internals() */ + return CURLE_OK; + } + } + + /* The protocol was not found in the table, but we don't have to assign it + to anything since it is already assigned to a dummy-struct in the + create_conn() function when the connectdata struct is allocated. */ + failf(data, "Protocol \"%s\" not supported or disabled in " LIBCURL_NAME, + protostr); + + return CURLE_UNSUPPORTED_PROTOCOL; +} + + +CURLcode Curl_uc_to_curlcode(CURLUcode uc) +{ + switch(uc) { + default: + return CURLE_URL_MALFORMAT; + case CURLUE_UNSUPPORTED_SCHEME: + return CURLE_UNSUPPORTED_PROTOCOL; + case CURLUE_OUT_OF_MEMORY: + return CURLE_OUT_OF_MEMORY; + case CURLUE_USER_NOT_ALLOWED: + return CURLE_LOGIN_DENIED; + } +} + +#ifdef ENABLE_IPV6 +/* + * If the URL was set with an IPv6 numerical address with a zone id part, set + * the scope_id based on that! + */ + +static void zonefrom_url(CURLU *uh, struct Curl_easy *data, + struct connectdata *conn) +{ + char *zoneid; + CURLUcode uc = curl_url_get(uh, CURLUPART_ZONEID, &zoneid, 0); +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)data; +#endif + + if(!uc && zoneid) { + char *endp; + unsigned long scope = strtoul(zoneid, &endp, 10); + if(!*endp && (scope < UINT_MAX)) + /* A plain number, use it directly as a scope id. */ + conn->scope_id = (unsigned int)scope; +#if defined(HAVE_IF_NAMETOINDEX) + else { +#elif defined(_WIN32) + else if(Curl_if_nametoindex) { +#endif + +#if defined(HAVE_IF_NAMETOINDEX) || defined(_WIN32) + /* Zone identifier is not numeric */ + unsigned int scopeidx = 0; +#if defined(_WIN32) + scopeidx = Curl_if_nametoindex(zoneid); +#else + scopeidx = if_nametoindex(zoneid); +#endif + if(!scopeidx) { +#ifndef CURL_DISABLE_VERBOSE_STRINGS + char buffer[STRERROR_LEN]; + infof(data, "Invalid zoneid: %s; %s", zoneid, + Curl_strerror(errno, buffer, sizeof(buffer))); +#endif + } + else + conn->scope_id = scopeidx; + } +#endif /* HAVE_IF_NAMETOINDEX || _WIN32 */ + + free(zoneid); + } +} +#else +#define zonefrom_url(a,b,c) Curl_nop_stmt +#endif + +/* + * Parse URL and fill in the relevant members of the connection struct. + */ +static CURLcode parseurlandfillconn(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result; + CURLU *uh; + CURLUcode uc; + char *hostname; + bool use_set_uh = (data->set.uh && !data->state.this_is_a_follow); + + up_free(data); /* cleanup previous leftovers first */ + + /* parse the URL */ + if(use_set_uh) { + uh = data->state.uh = curl_url_dup(data->set.uh); + } + else { + uh = data->state.uh = curl_url(); + } + + if(!uh) + return CURLE_OUT_OF_MEMORY; + + if(data->set.str[STRING_DEFAULT_PROTOCOL] && + !Curl_is_absolute_url(data->state.url, NULL, 0, TRUE)) { + char *url = aprintf("%s://%s", data->set.str[STRING_DEFAULT_PROTOCOL], + data->state.url); + if(!url) + return CURLE_OUT_OF_MEMORY; + if(data->state.url_alloc) + free(data->state.url); + data->state.url = url; + data->state.url_alloc = TRUE; + } + + if(!use_set_uh) { + char *newurl; + uc = curl_url_set(uh, CURLUPART_URL, data->state.url, + CURLU_GUESS_SCHEME | + CURLU_NON_SUPPORT_SCHEME | + (data->set.disallow_username_in_url ? + CURLU_DISALLOW_USER : 0) | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); + if(uc) { + failf(data, "URL rejected: %s", curl_url_strerror(uc)); + return Curl_uc_to_curlcode(uc); + } + + /* after it was parsed, get the generated normalized version */ + uc = curl_url_get(uh, CURLUPART_URL, &newurl, 0); + if(uc) + return Curl_uc_to_curlcode(uc); + if(data->state.url_alloc) + free(data->state.url); + data->state.url = newurl; + data->state.url_alloc = TRUE; + } + + uc = curl_url_get(uh, CURLUPART_SCHEME, &data->state.up.scheme, 0); + if(uc) + return Curl_uc_to_curlcode(uc); + + uc = curl_url_get(uh, CURLUPART_HOST, &data->state.up.hostname, 0); + if(uc) { + if(!strcasecompare("file", data->state.up.scheme)) + return CURLE_OUT_OF_MEMORY; + } + else if(strlen(data->state.up.hostname) > MAX_URL_LEN) { + failf(data, "Too long host name (maximum is %d)", MAX_URL_LEN); + return CURLE_URL_MALFORMAT; + } + hostname = data->state.up.hostname; + + if(hostname && hostname[0] == '[') { + /* This looks like an IPv6 address literal. See if there is an address + scope. */ + size_t hlen; + conn->bits.ipv6_ip = TRUE; + /* cut off the brackets! */ + hostname++; + hlen = strlen(hostname); + hostname[hlen - 1] = 0; + + zonefrom_url(uh, data, conn); + } + + /* make sure the connect struct gets its own copy of the host name */ + conn->host.rawalloc = strdup(hostname ? hostname : ""); + if(!conn->host.rawalloc) + return CURLE_OUT_OF_MEMORY; + conn->host.name = conn->host.rawalloc; + + /************************************************************* + * IDN-convert the hostnames + *************************************************************/ + result = Curl_idnconvert_hostname(&conn->host); + if(result) + return result; + +#ifndef CURL_DISABLE_HSTS + /* HSTS upgrade */ + if(data->hsts && strcasecompare("http", data->state.up.scheme)) { + /* This MUST use the IDN decoded name */ + if(Curl_hsts(data->hsts, conn->host.name, TRUE)) { + char *url; + Curl_safefree(data->state.up.scheme); + uc = curl_url_set(uh, CURLUPART_SCHEME, "https", 0); + if(uc) + return Curl_uc_to_curlcode(uc); + if(data->state.url_alloc) + Curl_safefree(data->state.url); + /* after update, get the updated version */ + uc = curl_url_get(uh, CURLUPART_URL, &url, 0); + if(uc) + return Curl_uc_to_curlcode(uc); + uc = curl_url_get(uh, CURLUPART_SCHEME, &data->state.up.scheme, 0); + if(uc) { + free(url); + return Curl_uc_to_curlcode(uc); + } + data->state.url = url; + data->state.url_alloc = TRUE; + infof(data, "Switched from HTTP to HTTPS due to HSTS => %s", + data->state.url); + } + } +#endif + + result = findprotocol(data, conn, data->state.up.scheme); + if(result) + return result; + + /* + * User name and password set with their own options override the + * credentials possibly set in the URL. + */ + if(!data->set.str[STRING_PASSWORD]) { + uc = curl_url_get(uh, CURLUPART_PASSWORD, &data->state.up.password, 0); + if(!uc) { + char *decoded; + result = Curl_urldecode(data->state.up.password, 0, &decoded, NULL, + conn->handler->flags&PROTOPT_USERPWDCTRL ? + REJECT_ZERO : REJECT_CTRL); + if(result) + return result; + conn->passwd = decoded; + result = Curl_setstropt(&data->state.aptr.passwd, decoded); + if(result) + return result; + } + else if(uc != CURLUE_NO_PASSWORD) + return Curl_uc_to_curlcode(uc); + } + + if(!data->set.str[STRING_USERNAME]) { + /* we don't use the URL API's URL decoder option here since it rejects + control codes and we want to allow them for some schemes in the user + and password fields */ + uc = curl_url_get(uh, CURLUPART_USER, &data->state.up.user, 0); + if(!uc) { + char *decoded; + result = Curl_urldecode(data->state.up.user, 0, &decoded, NULL, + conn->handler->flags&PROTOPT_USERPWDCTRL ? + REJECT_ZERO : REJECT_CTRL); + if(result) + return result; + conn->user = decoded; + result = Curl_setstropt(&data->state.aptr.user, decoded); + } + else if(uc != CURLUE_NO_USER) + return Curl_uc_to_curlcode(uc); + else if(data->state.aptr.passwd) { + /* no user was set but a password, set a blank user */ + result = Curl_setstropt(&data->state.aptr.user, ""); + } + if(result) + return result; + } + + uc = curl_url_get(uh, CURLUPART_OPTIONS, &data->state.up.options, + CURLU_URLDECODE); + if(!uc) { + conn->options = strdup(data->state.up.options); + if(!conn->options) + return CURLE_OUT_OF_MEMORY; + } + else if(uc != CURLUE_NO_OPTIONS) + return Curl_uc_to_curlcode(uc); + + uc = curl_url_get(uh, CURLUPART_PATH, &data->state.up.path, + CURLU_URLENCODE); + if(uc) + return Curl_uc_to_curlcode(uc); + + uc = curl_url_get(uh, CURLUPART_PORT, &data->state.up.port, + CURLU_DEFAULT_PORT); + if(uc) { + if(!strcasecompare("file", data->state.up.scheme)) + return CURLE_OUT_OF_MEMORY; + } + else { + unsigned long port = strtoul(data->state.up.port, NULL, 10); + conn->port = conn->remote_port = + (data->set.use_port && data->state.allow_port) ? + data->set.use_port : curlx_ultous(port); + } + + (void)curl_url_get(uh, CURLUPART_QUERY, &data->state.up.query, 0); + +#ifdef ENABLE_IPV6 + if(data->set.scope_id) + /* Override any scope that was set above. */ + conn->scope_id = data->set.scope_id; +#endif + + return CURLE_OK; +} + + +/* + * If we're doing a resumed transfer, we need to setup our stuff + * properly. + */ +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->rangestringalloc) + free(s->range); + + if(s->resume_from) + s->range = aprintf("%" CURL_FORMAT_CURL_OFF_T "-", s->resume_from); + else + s->range = strdup(data->set.str[STRING_SET_RANGE]); + + s->rangestringalloc = (s->range) ? TRUE : FALSE; + + if(!s->range) + return CURLE_OUT_OF_MEMORY; + + /* tell ourselves to fetch this range */ + s->use_range = TRUE; /* enable range download */ + } + else + s->use_range = FALSE; /* disable range download */ + + return CURLE_OK; +} + + +/* + * setup_connection_internals() - + * + * Setup connection internals specific to the requested protocol in the + * Curl_easy. This is inited and setup before the connection is made but + * is about the particular protocol that is to be used. + * + * This MUST get called after proxy magic has been figured out. + */ +static CURLcode setup_connection_internals(struct Curl_easy *data, + struct connectdata *conn) +{ + const struct Curl_handler *p; + CURLcode result; + + /* Perform setup complement if some. */ + p = conn->handler; + + if(p->setup_connection) { + result = (*p->setup_connection)(data, conn); + + if(result) + return result; + + p = conn->handler; /* May have changed. */ + } + + if(conn->port < 0) + /* we check for -1 here since if proxy was detected already, this + was very likely already set to the proxy port */ + conn->port = p->defport; + + return CURLE_OK; +} + +/* + * Curl_free_request_state() should free temp data that was allocated in the + * Curl_easy for this single request. + */ + +void Curl_free_request_state(struct Curl_easy *data) +{ + Curl_safefree(data->req.p.http); + Curl_safefree(data->req.newurl); +#ifndef CURL_DISABLE_DOH + if(data->req.doh) { + Curl_close(&data->req.doh->probe[0].easy); + Curl_close(&data->req.doh->probe[1].easy); + } +#endif + Curl_client_cleanup(data); +} + + +#ifndef CURL_DISABLE_PROXY + +#ifndef CURL_DISABLE_HTTP +/**************************************************************** +* Detect what (if any) proxy to use. Remember that this selects a host +* name and is not limited to HTTP proxies only. +* The returned pointer must be freed by the caller (unless NULL) +****************************************************************/ +static char *detect_proxy(struct Curl_easy *data, + struct connectdata *conn) +{ + char *proxy = NULL; + + /* If proxy was not specified, we check for default proxy environment + * variables, to enable i.e Lynx compliance: + * + * http_proxy=http://some.server.dom:port/ + * https_proxy=http://some.server.dom:port/ + * ftp_proxy=http://some.server.dom:port/ + * no_proxy=domain1.dom,host.domain2.dom + * (a comma-separated list of hosts which should + * not be proxied, or an asterisk to override + * all proxy variables) + * all_proxy=http://some.server.dom:port/ + * (seems to exist for the CERN www lib. Probably + * the first to check for.) + * + * For compatibility, the all-uppercase versions of these variables are + * checked if the lowercase versions don't exist. + */ + char proxy_env[128]; + const char *protop = conn->handler->scheme; + char *envp = proxy_env; +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)data; +#endif + + /* Now, build <protocol>_proxy and check for such a one to use */ + while(*protop) + *envp++ = Curl_raw_tolower(*protop++); + + /* append _proxy */ + strcpy(envp, "_proxy"); + + /* read the protocol proxy: */ + proxy = curl_getenv(proxy_env); + + /* + * We don't 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 && !strcasecompare("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); + } + + envp = proxy_env; + if(!proxy) { +#ifdef USE_WEBSOCKETS + /* websocket proxy fallbacks */ + if(strcasecompare("ws_proxy", proxy_env)) { + proxy = curl_getenv("http_proxy"); + } + else if(strcasecompare("wss_proxy", proxy_env)) { + proxy = curl_getenv("https_proxy"); + if(!proxy) + proxy = curl_getenv("HTTPS_PROXY"); + } + if(!proxy) { +#endif + envp = (char *)"all_proxy"; + proxy = curl_getenv(envp); /* default proxy to use */ + if(!proxy) { + envp = (char *)"ALL_PROXY"; + proxy = curl_getenv(envp); + } +#ifdef USE_WEBSOCKETS + } +#endif + } + if(proxy) + infof(data, "Uses proxy env variable %s == '%s'", envp, proxy); + + return proxy; +} +#endif /* CURL_DISABLE_HTTP */ + +/* + * If this is supposed to use a proxy, we need to figure out the proxy + * host name, so that we can reuse an existing connection + * that may exist registered to the same proxy host. + */ +static CURLcode parse_proxy(struct Curl_easy *data, + struct connectdata *conn, char *proxy, + curl_proxytype proxytype) +{ + char *portptr = NULL; + int port = -1; + char *proxyuser = NULL; + char *proxypasswd = NULL; + char *host = NULL; + bool sockstype; + CURLUcode uc; + struct proxy_info *proxyinfo; + CURLU *uhp = curl_url(); + CURLcode result = CURLE_OK; + char *scheme = NULL; +#ifdef USE_UNIX_SOCKETS + char *path = NULL; + bool is_unix_proxy = FALSE; +#endif + + + if(!uhp) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + /* When parsing the proxy, allowing non-supported schemes since we have + these made up ones for proxies. Guess scheme for URLs without it. */ + uc = curl_url_set(uhp, CURLUPART_URL, proxy, + CURLU_NON_SUPPORT_SCHEME|CURLU_GUESS_SCHEME); + if(!uc) { + /* parsed okay as a URL */ + uc = curl_url_get(uhp, CURLUPART_SCHEME, &scheme, 0); + if(uc) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + if(strcasecompare("https", scheme)) { + if(proxytype != CURLPROXY_HTTPS2) + proxytype = CURLPROXY_HTTPS; + else + proxytype = CURLPROXY_HTTPS2; + } + else if(strcasecompare("socks5h", scheme)) + proxytype = CURLPROXY_SOCKS5_HOSTNAME; + else if(strcasecompare("socks5", scheme)) + proxytype = CURLPROXY_SOCKS5; + else if(strcasecompare("socks4a", scheme)) + proxytype = CURLPROXY_SOCKS4A; + else if(strcasecompare("socks4", scheme) || + strcasecompare("socks", scheme)) + proxytype = CURLPROXY_SOCKS4; + else if(strcasecompare("http", scheme)) + ; /* leave it as HTTP or HTTP/1.0 */ + else { + /* Any other xxx:// reject! */ + failf(data, "Unsupported proxy scheme for \'%s\'", proxy); + result = CURLE_COULDNT_CONNECT; + goto error; + } + } + else { + failf(data, "Unsupported proxy syntax in \'%s\': %s", proxy, + curl_url_strerror(uc)); + result = CURLE_COULDNT_RESOLVE_PROXY; + goto error; + } + +#ifdef USE_SSL + if(!Curl_ssl_supports(data, SSLSUPP_HTTPS_PROXY)) +#endif + if(IS_HTTPS_PROXY(proxytype)) { + failf(data, "Unsupported proxy \'%s\', libcurl is built without the " + "HTTPS-proxy support.", proxy); + result = CURLE_NOT_BUILT_IN; + goto error; + } + + sockstype = + proxytype == CURLPROXY_SOCKS5_HOSTNAME || + proxytype == CURLPROXY_SOCKS5 || + proxytype == CURLPROXY_SOCKS4A || + proxytype == CURLPROXY_SOCKS4; + + proxyinfo = sockstype ? &conn->socks_proxy : &conn->http_proxy; + proxyinfo->proxytype = (unsigned char)proxytype; + + /* Is there a username and password given in this proxy url? */ + uc = curl_url_get(uhp, CURLUPART_USER, &proxyuser, CURLU_URLDECODE); + if(uc && (uc != CURLUE_NO_USER)) + goto error; + uc = curl_url_get(uhp, CURLUPART_PASSWORD, &proxypasswd, CURLU_URLDECODE); + if(uc && (uc != CURLUE_NO_PASSWORD)) + goto error; + + if(proxyuser || proxypasswd) { + Curl_safefree(proxyinfo->user); + proxyinfo->user = proxyuser; + result = Curl_setstropt(&data->state.aptr.proxyuser, proxyuser); + proxyuser = NULL; + if(result) + goto error; + Curl_safefree(proxyinfo->passwd); + if(!proxypasswd) { + proxypasswd = strdup(""); + if(!proxypasswd) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + } + proxyinfo->passwd = proxypasswd; + result = Curl_setstropt(&data->state.aptr.proxypasswd, proxypasswd); + proxypasswd = NULL; + if(result) + goto error; + conn->bits.proxy_user_passwd = TRUE; /* enable it */ + } + + (void)curl_url_get(uhp, CURLUPART_PORT, &portptr, 0); + + if(portptr) { + port = (int)strtol(portptr, NULL, 10); + free(portptr); + } + else { + if(data->set.proxyport) + /* None given in the proxy string, then get the default one if it is + given */ + port = (int)data->set.proxyport; + else { + if(IS_HTTPS_PROXY(proxytype)) + port = CURL_DEFAULT_HTTPS_PROXY_PORT; + else + port = CURL_DEFAULT_PROXY_PORT; + } + } + if(port >= 0) { + proxyinfo->port = port; + if(conn->port < 0 || sockstype || !conn->socks_proxy.host.rawalloc) + conn->port = port; + } + + /* now, clone the proxy host name */ + uc = curl_url_get(uhp, CURLUPART_HOST, &host, CURLU_URLDECODE); + if(uc) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } +#ifdef USE_UNIX_SOCKETS + if(sockstype && strcasecompare(UNIX_SOCKET_PREFIX, host)) { + uc = curl_url_get(uhp, CURLUPART_PATH, &path, CURLU_URLDECODE); + if(uc) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + /* path will be "/", if no path was found */ + if(strcmp("/", path)) { + is_unix_proxy = TRUE; + free(host); + host = aprintf(UNIX_SOCKET_PREFIX"%s", path); + if(!host) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + Curl_safefree(proxyinfo->host.rawalloc); + proxyinfo->host.rawalloc = host; + proxyinfo->host.name = host; + host = NULL; + } + } + + if(!is_unix_proxy) { +#endif + Curl_safefree(proxyinfo->host.rawalloc); + proxyinfo->host.rawalloc = host; + if(host[0] == '[') { + /* this is a numerical IPv6, strip off the brackets */ + size_t len = strlen(host); + host[len-1] = 0; /* clear the trailing bracket */ + host++; + zonefrom_url(uhp, data, conn); + } + proxyinfo->host.name = host; + host = NULL; +#ifdef USE_UNIX_SOCKETS + } +#endif + +error: + free(proxyuser); + free(proxypasswd); + free(host); + free(scheme); +#ifdef USE_UNIX_SOCKETS + free(path); +#endif + curl_url_cleanup(uhp); + return result; +} + +/* + * Extract the user and password from the authentication string + */ +static CURLcode parse_proxy_auth(struct Curl_easy *data, + struct connectdata *conn) +{ + const char *proxyuser = data->state.aptr.proxyuser ? + data->state.aptr.proxyuser : ""; + const char *proxypasswd = data->state.aptr.proxypasswd ? + data->state.aptr.proxypasswd : ""; + CURLcode result = Curl_urldecode(proxyuser, 0, &conn->http_proxy.user, NULL, + REJECT_ZERO); + if(!result) + result = Curl_setstropt(&data->state.aptr.proxyuser, + conn->http_proxy.user); + if(!result) + result = Curl_urldecode(proxypasswd, 0, &conn->http_proxy.passwd, + NULL, REJECT_ZERO); + if(!result) + result = Curl_setstropt(&data->state.aptr.proxypasswd, + conn->http_proxy.passwd); + return result; +} + +/* create_conn helper to parse and init proxy values. to be called after unix + socket init but before any proxy vars are evaluated. */ +static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, + struct connectdata *conn) +{ + char *proxy = NULL; + char *socksproxy = NULL; + char *no_proxy = NULL; + CURLcode result = CURLE_OK; + bool spacesep = FALSE; + + /************************************************************* + * Extract the user and password from the authentication string + *************************************************************/ + if(conn->bits.proxy_user_passwd) { + result = parse_proxy_auth(data, conn); + if(result) + goto out; + } + + /************************************************************* + * Detect what (if any) proxy to use + *************************************************************/ + if(data->set.str[STRING_PROXY]) { + proxy = strdup(data->set.str[STRING_PROXY]); + /* if global proxy is set, this is it */ + if(!proxy) { + failf(data, "memory shortage"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + + if(data->set.str[STRING_PRE_PROXY]) { + socksproxy = strdup(data->set.str[STRING_PRE_PROXY]); + /* if global socks proxy is set, this is it */ + if(!socksproxy) { + failf(data, "memory shortage"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + + if(!data->set.str[STRING_NOPROXY]) { + const char *p = "no_proxy"; + no_proxy = curl_getenv(p); + if(!no_proxy) { + p = "NO_PROXY"; + no_proxy = curl_getenv(p); + } + if(no_proxy) { + infof(data, "Uses proxy env variable %s == '%s'", p, no_proxy); + } + } + + if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ? + data->set.str[STRING_NOPROXY] : no_proxy, + &spacesep)) { + Curl_safefree(proxy); + Curl_safefree(socksproxy); + } +#ifndef CURL_DISABLE_HTTP + else if(!proxy && !socksproxy) + /* if the host is not in the noproxy list, detect proxy. */ + proxy = detect_proxy(data, conn); +#endif /* CURL_DISABLE_HTTP */ + if(spacesep) + infof(data, "space-separated NOPROXY patterns are deprecated"); + + Curl_safefree(no_proxy); + +#ifdef USE_UNIX_SOCKETS + /* For the time being do not mix proxy and unix domain sockets. See #1274 */ + if(proxy && conn->unix_domain_socket) { + free(proxy); + proxy = NULL; + } +#endif + + if(proxy && (!*proxy || (conn->handler->flags & PROTOPT_NONETWORK))) { + free(proxy); /* Don't bother with an empty proxy string or if the + protocol doesn't work with network */ + proxy = NULL; + } + if(socksproxy && (!*socksproxy || + (conn->handler->flags & PROTOPT_NONETWORK))) { + free(socksproxy); /* Don't bother with an empty socks proxy string or if + the protocol doesn't work with network */ + socksproxy = NULL; + } + + /*********************************************************************** + * If this is supposed to use a proxy, we need to figure out the proxy host + * name, proxy type and port number, so that we can reuse an existing + * connection that may exist registered to the same proxy host. + ***********************************************************************/ + if(proxy || socksproxy) { + curl_proxytype ptype = (curl_proxytype)conn->http_proxy.proxytype; + if(proxy) { + result = parse_proxy(data, conn, proxy, ptype); + Curl_safefree(proxy); /* parse_proxy copies the proxy string */ + if(result) + goto out; + } + + if(socksproxy) { + result = parse_proxy(data, conn, socksproxy, ptype); + /* parse_proxy copies the socks proxy string */ + Curl_safefree(socksproxy); + if(result) + goto out; + } + + if(conn->http_proxy.host.rawalloc) { +#ifdef CURL_DISABLE_HTTP + /* asking for an HTTP proxy is a bit funny when HTTP is disabled... */ + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; +#else + /* force this connection's protocol to become HTTP if compatible */ + if(!(conn->handler->protocol & PROTO_FAMILY_HTTP)) { + if((conn->handler->flags & PROTOPT_PROXY_AS_HTTP) && + !conn->bits.tunnel_proxy) + conn->handler = &Curl_handler_http; + else + /* if not converting to HTTP over the proxy, enforce tunneling */ + conn->bits.tunnel_proxy = TRUE; + } + conn->bits.httpproxy = TRUE; +#endif + } + else { + conn->bits.httpproxy = FALSE; /* not an HTTP proxy */ + conn->bits.tunnel_proxy = FALSE; /* no tunneling if not HTTP */ + } + + if(conn->socks_proxy.host.rawalloc) { + if(!conn->http_proxy.host.rawalloc) { + /* once a socks proxy */ + if(!conn->socks_proxy.user) { + conn->socks_proxy.user = conn->http_proxy.user; + conn->http_proxy.user = NULL; + Curl_safefree(conn->socks_proxy.passwd); + conn->socks_proxy.passwd = conn->http_proxy.passwd; + conn->http_proxy.passwd = NULL; + } + } + conn->bits.socksproxy = TRUE; + } + else + conn->bits.socksproxy = FALSE; /* not a socks proxy */ + } + else { + conn->bits.socksproxy = FALSE; + conn->bits.httpproxy = FALSE; + } + conn->bits.proxy = conn->bits.httpproxy || conn->bits.socksproxy; + + if(!conn->bits.proxy) { + /* we aren't using the proxy after all... */ + conn->bits.proxy = FALSE; + conn->bits.httpproxy = FALSE; + conn->bits.socksproxy = FALSE; + conn->bits.proxy_user_passwd = FALSE; + conn->bits.tunnel_proxy = FALSE; + /* CURLPROXY_HTTPS does not have its own flag in conn->bits, yet we need + to signal that CURLPROXY_HTTPS is not used for this connection */ + conn->http_proxy.proxytype = CURLPROXY_HTTP; + } + +out: + + free(socksproxy); + free(proxy); + return result; +} +#endif /* CURL_DISABLE_PROXY */ + +/* + * Curl_parse_login_details() + * + * This is used to parse a login string for user name, password and options in + * the following formats: + * + * user + * user:password + * user:password;options + * user;options + * user;options:password + * :password + * :password;options + * ;options + * ;options:password + * + * Parameters: + * + * login [in] - The login string. + * len [in] - The length of the login string. + * userp [in/out] - The address where a pointer to newly allocated memory + * holding the user will be stored upon completion. + * passwdp [in/out] - The address where a pointer to newly allocated memory + * holding the password will be stored upon completion. + * optionsp [in/out] - The address where a pointer to newly allocated memory + * holding the options will be stored upon completion. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_parse_login_details(const char *login, const size_t len, + char **userp, char **passwdp, + char **optionsp) +{ + CURLcode result = CURLE_OK; + char *ubuf = NULL; + char *pbuf = NULL; + char *obuf = NULL; + const char *psep = NULL; + const char *osep = NULL; + size_t ulen; + size_t plen; + size_t olen; + + /* Attempt to find the password separator */ + if(passwdp) + psep = memchr(login, ':', len); + + /* Attempt to find the options separator */ + if(optionsp) + osep = memchr(login, ';', len); + + /* Calculate the portion lengths */ + ulen = (psep ? + (size_t)(osep && psep > osep ? osep - login : psep - login) : + (osep ? (size_t)(osep - login) : len)); + plen = (psep ? + (osep && osep > psep ? (size_t)(osep - psep) : + (size_t)(login + len - psep)) - 1 : 0); + olen = (osep ? + (psep && psep > osep ? (size_t)(psep - osep) : + (size_t)(login + len - osep)) - 1 : 0); + + /* Allocate the user portion buffer, which can be zero length */ + if(userp) { + ubuf = malloc(ulen + 1); + if(!ubuf) + result = CURLE_OUT_OF_MEMORY; + } + + /* Allocate the password portion buffer */ + if(!result && passwdp && psep) { + pbuf = malloc(plen + 1); + if(!pbuf) { + free(ubuf); + result = CURLE_OUT_OF_MEMORY; + } + } + + /* Allocate the options portion buffer */ + if(!result && optionsp && olen) { + obuf = malloc(olen + 1); + if(!obuf) { + free(pbuf); + free(ubuf); + result = CURLE_OUT_OF_MEMORY; + } + } + + if(!result) { + /* Store the user portion if necessary */ + if(ubuf) { + memcpy(ubuf, login, ulen); + ubuf[ulen] = '\0'; + Curl_safefree(*userp); + *userp = ubuf; + } + + /* Store the password portion if necessary */ + if(pbuf) { + memcpy(pbuf, psep + 1, plen); + pbuf[plen] = '\0'; + Curl_safefree(*passwdp); + *passwdp = pbuf; + } + + /* Store the options portion if necessary */ + if(obuf) { + memcpy(obuf, osep + 1, olen); + obuf[olen] = '\0'; + Curl_safefree(*optionsp); + *optionsp = obuf; + } + } + + return result; +} + +/************************************************************* + * Figure out the remote port number and fix it in the URL + * + * No matter if we use a proxy or not, we have to figure out the remote + * port number of various reasons. + * + * The port number embedded in the URL is replaced, if necessary. + *************************************************************/ +static CURLcode parse_remote_port(struct Curl_easy *data, + struct connectdata *conn) +{ + + if(data->set.use_port && data->state.allow_port) { + /* if set, we use this instead of the port possibly given in the URL */ + char portbuf[16]; + CURLUcode uc; + conn->remote_port = data->set.use_port; + msnprintf(portbuf, sizeof(portbuf), "%d", conn->remote_port); + uc = curl_url_set(data->state.uh, CURLUPART_PORT, portbuf, 0); + if(uc) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +/* + * Override the login details from the URL with that in the CURLOPT_USERPWD + * option or a .netrc file, if applicable. + */ +static CURLcode override_login(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLUcode uc; + char **userp = &conn->user; + char **passwdp = &conn->passwd; + char **optionsp = &conn->options; + + if(data->set.str[STRING_OPTIONS]) { + free(*optionsp); + *optionsp = strdup(data->set.str[STRING_OPTIONS]); + if(!*optionsp) + return CURLE_OUT_OF_MEMORY; + } + +#ifndef CURL_DISABLE_NETRC + if(data->set.use_netrc == CURL_NETRC_REQUIRED) { + Curl_safefree(*userp); + Curl_safefree(*passwdp); + } + conn->bits.netrc = FALSE; + if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) { + int ret; + bool url_provided = FALSE; + + if(data->state.aptr.user) { + /* there was a user name in the URL. Use the URL decoded version */ + userp = &data->state.aptr.user; + url_provided = TRUE; + } + + ret = Curl_parsenetrc(conn->host.name, + userp, passwdp, + data->set.str[STRING_NETRC_FILE]); + if(ret > 0) { + infof(data, "Couldn't find host %s in the %s file; using defaults", + conn->host.name, + (data->set.str[STRING_NETRC_FILE] ? + data->set.str[STRING_NETRC_FILE] : ".netrc")); + } + else if(ret < 0) { + failf(data, ".netrc parser error"); + return CURLE_READ_ERROR; + } + else { + /* set bits.netrc TRUE to remember that we got the name from a .netrc + file, so that it is safe to use even if we followed a Location: to a + different host or similar. */ + conn->bits.netrc = TRUE; + } + if(url_provided) { + Curl_safefree(conn->user); + conn->user = strdup(*userp); + if(!conn->user) + return CURLE_OUT_OF_MEMORY; + } + /* no user was set but a password, set a blank user */ + if(!*userp && *passwdp) { + *userp = strdup(""); + if(!*userp) + return CURLE_OUT_OF_MEMORY; + } + } +#endif + + /* for updated strings, we update them in the URL */ + if(*userp) { + CURLcode result; + if(data->state.aptr.user != *userp) { + /* nothing to do then */ + result = Curl_setstropt(&data->state.aptr.user, *userp); + if(result) + return result; + } + } + if(data->state.aptr.user) { + uc = curl_url_set(data->state.uh, CURLUPART_USER, data->state.aptr.user, + CURLU_URLENCODE); + if(uc) + return Curl_uc_to_curlcode(uc); + if(!*userp) { + *userp = strdup(data->state.aptr.user); + if(!*userp) + return CURLE_OUT_OF_MEMORY; + } + } + if(*passwdp) { + CURLcode result = Curl_setstropt(&data->state.aptr.passwd, *passwdp); + if(result) + return result; + } + if(data->state.aptr.passwd) { + uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD, + data->state.aptr.passwd, CURLU_URLENCODE); + if(uc) + return Curl_uc_to_curlcode(uc); + if(!*passwdp) { + *passwdp = strdup(data->state.aptr.passwd); + if(!*passwdp) + return CURLE_OUT_OF_MEMORY; + } + } + + return CURLE_OK; +} + +/* + * Set the login details so they're available in the connection + */ +static CURLcode set_login(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + const char *setuser = CURL_DEFAULT_USER; + const char *setpasswd = CURL_DEFAULT_PASSWORD; + + /* If our protocol needs a password and we have none, use the defaults */ + if((conn->handler->flags & PROTOPT_NEEDSPWD) && !data->state.aptr.user) + ; + else { + setuser = ""; + setpasswd = ""; + } + /* Store the default user */ + if(!conn->user) { + conn->user = strdup(setuser); + if(!conn->user) + return CURLE_OUT_OF_MEMORY; + } + + /* Store the default password */ + if(!conn->passwd) { + conn->passwd = strdup(setpasswd); + if(!conn->passwd) + result = CURLE_OUT_OF_MEMORY; + } + + return result; +} + +/* + * Parses a "host:port" string to connect to. + * The hostname and the port may be empty; in this case, NULL is returned for + * the hostname and -1 for the port. + */ +static CURLcode parse_connect_to_host_port(struct Curl_easy *data, + const char *host, + char **hostname_result, + int *port_result) +{ + char *host_dup; + char *hostptr; + char *host_portno; + char *portptr; + int port = -1; + CURLcode result = CURLE_OK; + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) data; +#endif + + *hostname_result = NULL; + *port_result = -1; + + if(!host || !*host) + return CURLE_OK; + + host_dup = strdup(host); + if(!host_dup) + return CURLE_OUT_OF_MEMORY; + + hostptr = host_dup; + + /* start scanning for port number at this point */ + portptr = hostptr; + + /* detect and extract RFC6874-style IPv6-addresses */ + if(*hostptr == '[') { +#ifdef ENABLE_IPV6 + char *ptr = ++hostptr; /* advance beyond the initial bracket */ + while(*ptr && (ISXDIGIT(*ptr) || (*ptr == ':') || (*ptr == '.'))) + ptr++; + if(*ptr == '%') { + /* There might be a zone identifier */ + if(strncmp("%25", ptr, 3)) + infof(data, "Please URL encode %% as %%25, see RFC 6874."); + ptr++; + /* Allow unreserved characters as defined in RFC 3986 */ + while(*ptr && (ISALPHA(*ptr) || ISXDIGIT(*ptr) || (*ptr == '-') || + (*ptr == '.') || (*ptr == '_') || (*ptr == '~'))) + ptr++; + } + if(*ptr == ']') + /* yeps, it ended nicely with a bracket as well */ + *ptr++ = '\0'; + else + infof(data, "Invalid IPv6 address format"); + portptr = ptr; + /* Note that if this didn't end with a bracket, we still advanced the + * hostptr first, but I can't see anything wrong with that as no host + * name nor a numeric can legally start with a bracket. + */ +#else + failf(data, "Use of IPv6 in *_CONNECT_TO without IPv6 support built-in"); + result = CURLE_NOT_BUILT_IN; + goto error; +#endif + } + + /* Get port number off server.com:1080 */ + host_portno = strchr(portptr, ':'); + if(host_portno) { + char *endp = NULL; + *host_portno = '\0'; /* cut off number from host name */ + host_portno++; + if(*host_portno) { + long portparse = strtol(host_portno, &endp, 10); + if((endp && *endp) || (portparse < 0) || (portparse > 65535)) { + failf(data, "No valid port number in connect to host string (%s)", + host_portno); + result = CURLE_SETOPT_OPTION_SYNTAX; + goto error; + } + else + port = (int)portparse; /* we know it will fit */ + } + } + + /* now, clone the cleaned host name */ + DEBUGASSERT(hostptr); + *hostname_result = strdup(hostptr); + if(!*hostname_result) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + *port_result = port; + +error: + free(host_dup); + return result; +} + +/* + * Parses one "connect to" string in the form: + * "HOST:PORT:CONNECT-TO-HOST:CONNECT-TO-PORT". + */ +static CURLcode parse_connect_to_string(struct Curl_easy *data, + struct connectdata *conn, + const char *conn_to_host, + char **host_result, + int *port_result) +{ + CURLcode result = CURLE_OK; + const char *ptr = conn_to_host; + int host_match = FALSE; + int port_match = FALSE; + + *host_result = NULL; + *port_result = -1; + + if(*ptr == ':') { + /* an empty hostname always matches */ + host_match = TRUE; + ptr++; + } + else { + /* check whether the URL's hostname matches */ + size_t hostname_to_match_len; + char *hostname_to_match = aprintf("%s%s%s", + conn->bits.ipv6_ip ? "[" : "", + conn->host.name, + conn->bits.ipv6_ip ? "]" : ""); + if(!hostname_to_match) + return CURLE_OUT_OF_MEMORY; + hostname_to_match_len = strlen(hostname_to_match); + host_match = strncasecompare(ptr, hostname_to_match, + hostname_to_match_len); + free(hostname_to_match); + ptr += hostname_to_match_len; + + host_match = host_match && *ptr == ':'; + ptr++; + } + + if(host_match) { + if(*ptr == ':') { + /* an empty port always matches */ + port_match = TRUE; + ptr++; + } + else { + /* check whether the URL's port matches */ + char *ptr_next = strchr(ptr, ':'); + if(ptr_next) { + char *endp = NULL; + long port_to_match = strtol(ptr, &endp, 10); + if((endp == ptr_next) && (port_to_match == conn->remote_port)) { + port_match = TRUE; + ptr = ptr_next + 1; + } + } + } + } + + if(host_match && port_match) { + /* parse the hostname and port to connect to */ + result = parse_connect_to_host_port(data, ptr, host_result, port_result); + } + + return result; +} + +/* + * Processes all strings in the "connect to" slist, and uses the "connect + * to host" and "connect to port" of the first string that matches. + */ +static CURLcode parse_connect_to_slist(struct Curl_easy *data, + struct connectdata *conn, + struct curl_slist *conn_to_host) +{ + CURLcode result = CURLE_OK; + char *host = NULL; + int port = -1; + + while(conn_to_host && !host && port == -1) { + result = parse_connect_to_string(data, conn, conn_to_host->data, + &host, &port); + if(result) + return result; + + if(host && *host) { + conn->conn_to_host.rawalloc = host; + conn->conn_to_host.name = host; + conn->bits.conn_to_host = TRUE; + + infof(data, "Connecting to hostname: %s", host); + } + else { + /* no "connect to host" */ + conn->bits.conn_to_host = FALSE; + Curl_safefree(host); + } + + if(port >= 0) { + conn->conn_to_port = port; + conn->bits.conn_to_port = TRUE; + infof(data, "Connecting to port: %d", port); + } + else { + /* no "connect to port" */ + conn->bits.conn_to_port = FALSE; + port = -1; + } + + conn_to_host = conn_to_host->next; + } + +#ifndef CURL_DISABLE_ALTSVC + if(data->asi && !host && (port == -1) && + ((conn->handler->protocol == CURLPROTO_HTTPS) || +#ifdef CURLDEBUG + /* allow debug builds to circumvent the HTTPS restriction */ + getenv("CURL_ALTSVC_HTTP") +#else + 0 +#endif + )) { + /* no connect_to match, try alt-svc! */ + enum alpnid srcalpnid; + bool hit; + struct altsvc *as; + const int allowed_versions = ( ALPN_h1 +#ifdef USE_HTTP2 + | ALPN_h2 +#endif +#ifdef ENABLE_QUIC + | ALPN_h3 +#endif + ) & data->asi->flags; + + host = conn->host.rawalloc; +#ifdef USE_HTTP2 + /* with h2 support, check that first */ + srcalpnid = ALPN_h2; + hit = Curl_altsvc_lookup(data->asi, + srcalpnid, host, conn->remote_port, /* from */ + &as /* to */, + allowed_versions); + if(!hit) +#endif + { + srcalpnid = ALPN_h1; + hit = Curl_altsvc_lookup(data->asi, + srcalpnid, host, conn->remote_port, /* from */ + &as /* to */, + allowed_versions); + } + if(hit) { + char *hostd = strdup((char *)as->dst.host); + if(!hostd) + return CURLE_OUT_OF_MEMORY; + conn->conn_to_host.rawalloc = hostd; + conn->conn_to_host.name = hostd; + conn->bits.conn_to_host = TRUE; + conn->conn_to_port = as->dst.port; + conn->bits.conn_to_port = TRUE; + conn->bits.altused = TRUE; + infof(data, "Alt-svc connecting from [%s]%s:%d to [%s]%s:%d", + Curl_alpnid2str(srcalpnid), host, conn->remote_port, + Curl_alpnid2str(as->dst.alpnid), hostd, as->dst.port); + if(srcalpnid != as->dst.alpnid) { + /* protocol version switch */ + switch(as->dst.alpnid) { + case ALPN_h1: + conn->httpversion = 11; + break; + case ALPN_h2: + conn->httpversion = 20; + break; + case ALPN_h3: + conn->transport = TRNSPRT_QUIC; + conn->httpversion = 30; + break; + default: /* shouldn't be possible */ + break; + } + } + } + } +#endif + + return result; +} + +#ifdef USE_UNIX_SOCKETS +static CURLcode resolve_unix(struct Curl_easy *data, + struct connectdata *conn, + char *unix_path) +{ + struct Curl_dns_entry *hostaddr = NULL; + bool longpath = FALSE; + + DEBUGASSERT(unix_path); + DEBUGASSERT(conn->dns_entry == NULL); + + /* Unix domain sockets are local. The host gets ignored, just use the + * specified domain socket address. Do not cache "DNS entries". There is + * no DNS involved and we already have the filesystem path available. */ + hostaddr = calloc(1, sizeof(struct Curl_dns_entry)); + if(!hostaddr) + return CURLE_OUT_OF_MEMORY; + + hostaddr->addr = Curl_unix2addr(unix_path, &longpath, + conn->bits.abstract_unix_socket); + if(!hostaddr->addr) { + if(longpath) + /* Long paths are not supported for now */ + failf(data, "Unix socket path too long: '%s'", unix_path); + free(hostaddr); + return longpath ? CURLE_COULDNT_RESOLVE_HOST : CURLE_OUT_OF_MEMORY; + } + + hostaddr->inuse++; + conn->dns_entry = hostaddr; + return CURLE_OK; +} +#endif + +#ifndef CURL_DISABLE_PROXY +static CURLcode resolve_proxy(struct Curl_easy *data, + struct connectdata *conn, + bool *async) +{ + struct Curl_dns_entry *hostaddr = NULL; + struct hostname *host; + timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + int rc; + + DEBUGASSERT(conn->dns_entry == NULL); + + host = conn->bits.socksproxy ? &conn->socks_proxy.host : + &conn->http_proxy.host; + + conn->hostname_resolve = strdup(host->name); + if(!conn->hostname_resolve) + return CURLE_OUT_OF_MEMORY; + + rc = Curl_resolv_timeout(data, conn->hostname_resolve, (int)conn->port, + &hostaddr, timeout_ms); + conn->dns_entry = hostaddr; + if(rc == CURLRESOLV_PENDING) + *async = TRUE; + else if(rc == CURLRESOLV_TIMEDOUT) + return CURLE_OPERATION_TIMEDOUT; + else if(!hostaddr) { + failf(data, "Couldn't resolve proxy '%s'", host->dispname); + return CURLE_COULDNT_RESOLVE_PROXY; + } + + return CURLE_OK; +} +#endif + +static CURLcode resolve_host(struct Curl_easy *data, + struct connectdata *conn, + bool *async) +{ + struct Curl_dns_entry *hostaddr = NULL; + struct hostname *connhost; + timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + int rc; + + DEBUGASSERT(conn->dns_entry == NULL); + + connhost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host; + + /* If not connecting via a proxy, extract the port from the URL, if it is + * there, thus overriding any defaults that might have been set above. */ + conn->port = conn->bits.conn_to_port ? conn->conn_to_port : + conn->remote_port; + + /* Resolve target host right on */ + conn->hostname_resolve = strdup(connhost->name); + if(!conn->hostname_resolve) + return CURLE_OUT_OF_MEMORY; + + rc = Curl_resolv_timeout(data, conn->hostname_resolve, (int)conn->port, + &hostaddr, timeout_ms); + conn->dns_entry = hostaddr; + if(rc == CURLRESOLV_PENDING) + *async = TRUE; + else if(rc == CURLRESOLV_TIMEDOUT) { + failf(data, "Failed to resolve host '%s' with timeout after %" + CURL_FORMAT_TIMEDIFF_T " ms", connhost->dispname, + Curl_timediff(Curl_now(), data->progress.t_startsingle)); + return CURLE_OPERATION_TIMEDOUT; + } + else if(!hostaddr) { + failf(data, "Could not resolve host: %s", connhost->dispname); + return CURLE_COULDNT_RESOLVE_HOST; + } + + return CURLE_OK; +} + +/* Perform a fresh resolve */ +static CURLcode resolve_fresh(struct Curl_easy *data, + struct connectdata *conn, + bool *async) +{ +#ifdef USE_UNIX_SOCKETS + char *unix_path = conn->unix_domain_socket; + +#ifndef CURL_DISABLE_PROXY + if(!unix_path && conn->socks_proxy.host.name && + !strncmp(UNIX_SOCKET_PREFIX"/", + conn->socks_proxy.host.name, sizeof(UNIX_SOCKET_PREFIX))) + unix_path = conn->socks_proxy.host.name + sizeof(UNIX_SOCKET_PREFIX) - 1; +#endif + + if(unix_path) { + conn->transport = TRNSPRT_UNIX; + return resolve_unix(data, conn, unix_path); + } +#endif + +#ifndef CURL_DISABLE_PROXY + if(CONN_IS_PROXIED(conn)) + return resolve_proxy(data, conn, async); +#endif + + return resolve_host(data, conn, async); +} + +/************************************************************* + * Resolve the address of the server or proxy + *************************************************************/ +static CURLcode resolve_server(struct Curl_easy *data, + struct connectdata *conn, + bool *async) +{ + DEBUGASSERT(conn); + DEBUGASSERT(data); + + /* Resolve the name of the server or proxy */ + if(conn->bits.reuse) { + /* We're reusing the connection - no need to resolve anything, and + idnconvert_hostname() was called already in create_conn() for the reuse + case. */ + *async = FALSE; + return CURLE_OK; + } + + return resolve_fresh(data, conn, async); +} + +/* + * Cleanup the connection `temp`, just allocated for `data`, before using the + * previously `existing` one for `data`. All relevant info is copied over + * and `temp` is freed. + */ +static void reuse_conn(struct Curl_easy *data, + struct connectdata *temp, + struct connectdata *existing) +{ + /* get the user+password information from the temp struct since it may + * be new for this request even when we reuse an existing connection */ + if(temp->user) { + /* use the new user name and password though */ + Curl_safefree(existing->user); + Curl_safefree(existing->passwd); + existing->user = temp->user; + existing->passwd = temp->passwd; + temp->user = NULL; + temp->passwd = NULL; + } + +#ifndef CURL_DISABLE_PROXY + existing->bits.proxy_user_passwd = temp->bits.proxy_user_passwd; + if(existing->bits.proxy_user_passwd) { + /* use the new proxy user name and proxy password though */ + Curl_safefree(existing->http_proxy.user); + Curl_safefree(existing->socks_proxy.user); + Curl_safefree(existing->http_proxy.passwd); + Curl_safefree(existing->socks_proxy.passwd); + existing->http_proxy.user = temp->http_proxy.user; + existing->socks_proxy.user = temp->socks_proxy.user; + existing->http_proxy.passwd = temp->http_proxy.passwd; + existing->socks_proxy.passwd = temp->socks_proxy.passwd; + temp->http_proxy.user = NULL; + temp->socks_proxy.user = NULL; + temp->http_proxy.passwd = NULL; + temp->socks_proxy.passwd = NULL; + } +#endif + + /* Finding a connection for reuse in the cache matches, among other + * things on the "remote-relevant" hostname. This is not necessarily + * the authority of the URL, e.g. conn->host. For example: + * - we use a proxy (not tunneling). we want to send all requests + * that use the same proxy on this connection. + * - we have a "connect-to" setting that may redirect the hostname of + * a new request to the same remote endpoint of an existing conn. + * We want to reuse an existing conn to the remote endpoint. + * Since connection reuse does not match on conn->host necessarily, we + * switch `existing` conn to `temp` conn's host settings. + * TODO: is this correct in the case of TLS connections that have + * used the original hostname in SNI to negotiate? Do we send + * requests for another host through the different SNI? + */ + Curl_free_idnconverted_hostname(&existing->host); + Curl_free_idnconverted_hostname(&existing->conn_to_host); + Curl_safefree(existing->host.rawalloc); + Curl_safefree(existing->conn_to_host.rawalloc); + existing->host = temp->host; + temp->host.rawalloc = NULL; + temp->host.encalloc = NULL; + existing->conn_to_host = temp->conn_to_host; + temp->conn_to_host.rawalloc = NULL; + existing->conn_to_port = temp->conn_to_port; + existing->remote_port = temp->remote_port; + Curl_safefree(existing->hostname_resolve); + + existing->hostname_resolve = temp->hostname_resolve; + temp->hostname_resolve = NULL; + + /* reuse init */ + existing->bits.reuse = TRUE; /* yes, we're reusing here */ + + conn_free(data, temp); +} + +/** + * create_conn() sets up a new connectdata struct, or reuses an already + * existing one, and resolves host name. + * + * if this function returns CURLE_OK and *async is set to TRUE, the resolve + * response will be coming asynchronously. If *async is FALSE, the name is + * already resolved. + * + * @param data The sessionhandle pointer + * @param in_connect is set to the next connection data pointer + * @param async is set TRUE when an async DNS resolution is pending + * @see Curl_setup_conn() + * + */ + +static CURLcode create_conn(struct Curl_easy *data, + struct connectdata **in_connect, + bool *async) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn; + struct connectdata *existing = NULL; + bool reuse; + bool connections_available = TRUE; + bool force_reuse = FALSE; + bool waitpipe = FALSE; + size_t max_host_connections = Curl_multi_max_host_connections(data->multi); + size_t max_total_connections = Curl_multi_max_total_connections(data->multi); + + *async = FALSE; + *in_connect = NULL; + + /************************************************************* + * Check input data + *************************************************************/ + if(!data->state.url) { + result = CURLE_URL_MALFORMAT; + goto out; + } + + /* 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. */ + conn = allocate_conn(data); + + if(!conn) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + /* We must set the return variable as soon as possible, so that our + parent can cleanup any possible allocs we may have done before + any failure */ + *in_connect = conn; + + result = parseurlandfillconn(data, conn); + if(result) + goto out; + + if(data->set.str[STRING_SASL_AUTHZID]) { + conn->sasl_authzid = strdup(data->set.str[STRING_SASL_AUTHZID]); + if(!conn->sasl_authzid) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + + if(data->set.str[STRING_BEARER]) { + conn->oauth_bearer = strdup(data->set.str[STRING_BEARER]); + if(!conn->oauth_bearer) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + +#ifdef USE_UNIX_SOCKETS + if(data->set.str[STRING_UNIX_SOCKET_PATH]) { + conn->unix_domain_socket = strdup(data->set.str[STRING_UNIX_SOCKET_PATH]); + if(!conn->unix_domain_socket) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + conn->bits.abstract_unix_socket = data->set.abstract_unix_socket; + } +#endif + + /* After the unix socket init but before the proxy vars are used, parse and + initialize the proxy vars */ +#ifndef CURL_DISABLE_PROXY + result = create_conn_helper_init_proxy(data, conn); + if(result) + goto out; + + /************************************************************* + * If the protocol is using SSL and HTTP proxy is used, we set + * the tunnel_proxy bit. + *************************************************************/ + if((conn->given->flags&PROTOPT_SSL) && conn->bits.httpproxy) + conn->bits.tunnel_proxy = TRUE; +#endif + + /************************************************************* + * Figure out the remote port number and fix it in the URL + *************************************************************/ + result = parse_remote_port(data, conn); + if(result) + goto out; + + /* Check for overridden login details and set them accordingly so that + they are known when protocol->setup_connection is called! */ + result = override_login(data, conn); + if(result) + goto out; + + result = set_login(data, conn); /* default credentials */ + if(result) + goto out; + + /************************************************************* + * Process the "connect to" linked list of hostname/port mappings. + * Do this after the remote port number has been fixed in the URL. + *************************************************************/ + result = parse_connect_to_slist(data, conn, data->set.connect_to); + if(result) + goto out; + + /************************************************************* + * IDN-convert the proxy hostnames + *************************************************************/ +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy) { + result = Curl_idnconvert_hostname(&conn->http_proxy.host); + if(result) + return result; + } + if(conn->bits.socksproxy) { + result = Curl_idnconvert_hostname(&conn->socks_proxy.host); + if(result) + return result; + } +#endif + if(conn->bits.conn_to_host) { + result = Curl_idnconvert_hostname(&conn->conn_to_host); + if(result) + return result; + } + + /************************************************************* + * Check whether the host and the "connect to host" are equal. + * Do this after the hostnames have been IDN-converted. + *************************************************************/ + if(conn->bits.conn_to_host && + strcasecompare(conn->conn_to_host.name, conn->host.name)) { + conn->bits.conn_to_host = FALSE; + } + + /************************************************************* + * Check whether the port and the "connect to port" are equal. + * Do this after the remote port number has been fixed in the URL. + *************************************************************/ + if(conn->bits.conn_to_port && conn->conn_to_port == conn->remote_port) { + conn->bits.conn_to_port = FALSE; + } + +#ifndef CURL_DISABLE_PROXY + /************************************************************* + * If the "connect to" feature is used with an HTTP proxy, + * we set the tunnel_proxy bit. + *************************************************************/ + if((conn->bits.conn_to_host || conn->bits.conn_to_port) && + conn->bits.httpproxy) + conn->bits.tunnel_proxy = TRUE; +#endif + + /************************************************************* + * Setup internals depending on protocol. Needs to be done after + * we figured out what/if proxy to use. + *************************************************************/ + result = setup_connection_internals(data, conn); + if(result) + goto out; + + /*********************************************************************** + * file: is a special case in that it doesn't need a network connection + ***********************************************************************/ +#ifndef CURL_DISABLE_FILE + if(conn->handler->flags & PROTOPT_NONETWORK) { + bool done; + /* this is supposed to be the connect function so we better at least check + that the file is present here! */ + DEBUGASSERT(conn->handler->connect_it); + Curl_persistconninfo(data, conn, NULL, -1); + result = conn->handler->connect_it(data, &done); + + /* Setup a "faked" transfer that'll do nothing */ + if(!result) { + Curl_attach_connection(data, conn); + result = Curl_conncache_add_conn(data); + if(result) + goto out; + + /* + * Setup whatever necessary for a resumed transfer + */ + result = setup_range(data); + if(result) { + DEBUGASSERT(conn->handler->done); + /* we ignore the return code for the protocol-specific DONE */ + (void)conn->handler->done(data, result, FALSE); + goto out; + } + Curl_setup_transfer(data, -1, -1, FALSE, -1); + } + + /* since we skip do_init() */ + Curl_init_do(data, conn); + + goto out; + } +#endif + + /* Setup filter for network connections */ + conn->recv[FIRSTSOCKET] = Curl_conn_recv; + conn->send[FIRSTSOCKET] = Curl_conn_send; + conn->recv[SECONDARYSOCKET] = Curl_conn_recv; + conn->send[SECONDARYSOCKET] = Curl_conn_send; + conn->bits.tcp_fastopen = data->set.tcp_fastopen; + + /* Complete the easy's SSL configuration for connection cache matching */ + result = Curl_ssl_easy_config_complete(data); + if(result) + goto out; + + prune_dead_connections(data); + + /************************************************************* + * Check the current list of connections to see if we can + * reuse an already existing one or if we have to create a + * new one. + *************************************************************/ + + DEBUGASSERT(conn->user); + DEBUGASSERT(conn->passwd); + + /* reuse_fresh is TRUE if we are told to use a new connection by force, but + we only acknowledge this option if this is not a reused connection + already (which happens due to follow-location or during an HTTP + authentication phase). CONNECT_ONLY transfers also refuse reuse. */ + if((data->set.reuse_fresh && !data->state.followlocation) || + data->set.connect_only) + reuse = FALSE; + else + reuse = ConnectionExists(data, conn, &existing, &force_reuse, &waitpipe); + + if(reuse) { + /* + * We already have a connection for this, we got the former connection in + * `existing` and thus we need to cleanup the one we just + * allocated before we can move along and use `existing`. + */ + reuse_conn(data, conn, existing); + conn = existing; + *in_connect = conn; + +#ifndef CURL_DISABLE_PROXY + infof(data, "Re-using existing connection with %s %s", + conn->bits.proxy?"proxy":"host", + conn->socks_proxy.host.name ? conn->socks_proxy.host.dispname : + conn->http_proxy.host.name ? conn->http_proxy.host.dispname : + conn->host.dispname); +#else + infof(data, "Re-using existing connection with host %s", + conn->host.dispname); +#endif + } + else { + /* We have decided that we want a new connection. However, we may not + be able to do that if we have reached the limit of how many + connections we are allowed to open. */ + + if(conn->handler->flags & PROTOPT_ALPN) { + /* The protocol wants it, so set the bits if enabled in the easy handle + (default) */ + if(data->set.ssl_enable_alpn) + conn->bits.tls_enable_alpn = TRUE; + } + + if(waitpipe) + /* There is a connection that *might* become usable for multiplexing + "soon", and we wait for that */ + connections_available = FALSE; + else { + /* this gets a lock on the conncache */ + struct connectbundle *bundle = + Curl_conncache_find_bundle(data, conn, data->state.conn_cache); + + if(max_host_connections > 0 && bundle && + (bundle->num_connections >= max_host_connections)) { + struct connectdata *conn_candidate; + + /* The bundle is full. Extract the oldest connection. */ + conn_candidate = Curl_conncache_extract_bundle(data, bundle); + CONNCACHE_UNLOCK(data); + + if(conn_candidate) + Curl_disconnect(data, conn_candidate, FALSE); + else { + infof(data, "No more connections allowed to host: %zu", + max_host_connections); + connections_available = FALSE; + } + } + else + CONNCACHE_UNLOCK(data); + + } + + if(connections_available && + (max_total_connections > 0) && + (Curl_conncache_size(data) >= max_total_connections)) { + struct connectdata *conn_candidate; + + /* The cache is full. Let's see if we can kill a connection. */ + conn_candidate = Curl_conncache_extract_oldest(data); + if(conn_candidate) + Curl_disconnect(data, conn_candidate, FALSE); + else { + infof(data, "No connections available in cache"); + connections_available = FALSE; + } + } + + if(!connections_available) { + infof(data, "No connections available."); + + conn_free(data, conn); + *in_connect = NULL; + + result = CURLE_NO_CONNECTION_AVAILABLE; + goto out; + } + else { + /* + * This is a brand new connection, so let's store it in the connection + * cache of ours! + */ + result = Curl_ssl_conn_config_init(data, conn); + if(result) { + DEBUGF(fprintf(stderr, "Error: init connection ssl config\n")); + goto out; + } + + result = Curl_resolver_init(data, &conn->resolve_async.resolver); + if(result) { + DEBUGF(fprintf(stderr, "Error: resolver_init failed\n")); + goto out; + } + + Curl_attach_connection(data, conn); + +#ifdef USE_ARES + result = Curl_set_dns_servers(data, data->set.str[STRING_DNS_SERVERS]); + if(result && result != CURLE_NOT_BUILT_IN) + goto out; + + result = Curl_set_dns_interface(data, + data->set.str[STRING_DNS_INTERFACE]); + if(result && result != CURLE_NOT_BUILT_IN) + goto out; + + result = Curl_set_dns_local_ip4(data, + data->set.str[STRING_DNS_LOCAL_IP4]); + if(result && result != CURLE_NOT_BUILT_IN) + goto out; + + result = Curl_set_dns_local_ip6(data, + data->set.str[STRING_DNS_LOCAL_IP6]); + if(result && result != CURLE_NOT_BUILT_IN) + goto out; +#endif /* USE_ARES */ + + result = Curl_conncache_add_conn(data); + if(result) + goto out; + } + +#if defined(USE_NTLM) + /* If NTLM is requested in a part of this connection, make sure we don't + assume the state is fine as this is a fresh connection and NTLM is + connection based. */ + if((data->state.authhost.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + data->state.authhost.done) { + infof(data, "NTLM picked AND auth done set, clear picked"); + data->state.authhost.picked = CURLAUTH_NONE; + data->state.authhost.done = FALSE; + } + + if((data->state.authproxy.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + data->state.authproxy.done) { + infof(data, "NTLM-proxy picked AND auth done set, clear picked"); + data->state.authproxy.picked = CURLAUTH_NONE; + data->state.authproxy.done = FALSE; + } +#endif + } + + /* Setup and init stuff before DO starts, in preparing for the transfer. */ + Curl_init_do(data, conn); + + /* + * Setup whatever necessary for a resumed transfer + */ + result = setup_range(data); + if(result) + goto out; + + /* Continue connectdata initialization here. */ + + /* + * Inherit the proper values from the urldata struct AFTER we have arranged + * the persistent connection stuff + */ + conn->seek_func = data->set.seek_func; + conn->seek_client = data->set.seek_client; + + /************************************************************* + * Resolve the address of the server or proxy + *************************************************************/ + result = resolve_server(data, conn, async); + if(result) + goto out; + + /* Everything general done, inform filters that they need + * to prepare for a data transfer. + */ + result = Curl_conn_ev_data_setup(data); + +out: + return result; +} + +/* Curl_setup_conn() is called after the name resolve initiated in + * create_conn() is all done. + * + * Curl_setup_conn() also handles reused connections + */ +CURLcode Curl_setup_conn(struct Curl_easy *data, + bool *protocol_done) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + + Curl_pgrsTime(data, TIMER_NAMELOOKUP); + + if(conn->handler->flags & PROTOPT_NONETWORK) { + /* nothing to setup when not using a network */ + *protocol_done = TRUE; + return result; + } + +#ifndef CURL_DISABLE_PROXY + /* set proxy_connect_closed to false unconditionally already here since it + is used strictly to provide extra information to a parent function in the + case of proxy CONNECT failures and we must make sure we don't have it + lingering set from a previous invoke */ + conn->bits.proxy_connect_closed = FALSE; +#endif + +#ifdef CURL_DO_LINEEND_CONV + data->state.crlf_conversions = 0; /* reset CRLF conversion counter */ +#endif /* CURL_DO_LINEEND_CONV */ + + /* set start time here for timeout purposes in the connect procedure, it + is later set again for the progress meter purpose */ + conn->now = Curl_now(); + if(!conn->bits.reuse) + result = Curl_conn_setup(data, conn, FIRSTSOCKET, conn->dns_entry, + CURL_CF_SSL_DEFAULT); + /* not sure we need this flag to be passed around any more */ + *protocol_done = FALSE; + return result; +} + +CURLcode Curl_connect(struct Curl_easy *data, + bool *asyncp, + bool *protocol_done) +{ + CURLcode result; + struct connectdata *conn; + + *asyncp = FALSE; /* assume synchronous resolves by default */ + + /* init the single-transfer specific data */ + Curl_free_request_state(data); + memset(&data->req, 0, sizeof(struct SingleRequest)); + data->req.size = data->req.maxdownload = -1; + data->req.no_body = data->set.opt_no_body; + + /* call the stuff that needs to be called */ + result = create_conn(data, &conn, asyncp); + + if(!result) { + if(CONN_INUSE(conn) > 1) + /* multiplexed */ + *protocol_done = TRUE; + else if(!*asyncp) { + /* DNS resolution is done: that's either because this is a reused + connection, in which case DNS was unnecessary, or because DNS + really did finish already (synch resolver/fast async resolve) */ + result = Curl_setup_conn(data, protocol_done); + } + } + + if(result == CURLE_NO_CONNECTION_AVAILABLE) { + return result; + } + else if(result && conn) { + /* We're not allowed to return failure with memory left allocated in the + connectdata struct, free those here */ + Curl_detach_connection(data); + Curl_conncache_remove_conn(data, conn, TRUE); + Curl_disconnect(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. + * + * Allow this function to get called with 'conn' set to NULL. + */ + +CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn) +{ + struct SingleRequest *k = &data->req; + + /* if this is a pushed stream, we need this: */ + CURLcode result = Curl_preconnect(data); + if(result) + return result; + + if(conn) { + conn->bits.do_more = FALSE; /* by default there's no curl_do_more() to + use */ + /* if the protocol used doesn't support wildcards, switch it off */ + if(data->state.wildcardmatch && + !(conn->handler->flags & PROTOPT_WILDCARD)) + data->state.wildcardmatch = FALSE; + } + + data->state.done = FALSE; /* *_done() is not called yet */ + data->state.expect100header = FALSE; + + if(data->req.no_body) + /* in HTTP lingo, no body means using the HEAD request... */ + data->state.httpreq = HTTPREQ_HEAD; + + k->start = Curl_now(); /* start time */ + k->header = TRUE; /* assume header */ + k->bytecount = 0; + k->ignorebody = FALSE; + + Curl_client_cleanup(data); + Curl_speedinit(data); + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + + return CURLE_OK; +} + +#if defined(USE_HTTP2) || defined(USE_HTTP3) + +#ifdef USE_NGHTTP2 + +static void priority_remove_child(struct Curl_easy *parent, + struct Curl_easy *child) +{ + struct Curl_data_prio_node **pnext = &parent->set.priority.children; + struct Curl_data_prio_node *pnode = parent->set.priority.children; + + DEBUGASSERT(child->set.priority.parent == parent); + while(pnode && pnode->data != child) { + pnext = &pnode->next; + pnode = pnode->next; + } + + DEBUGASSERT(pnode); + if(pnode) { + *pnext = pnode->next; + free(pnode); + } + + child->set.priority.parent = 0; + child->set.priority.exclusive = FALSE; +} + +CURLcode Curl_data_priority_add_child(struct Curl_easy *parent, + struct Curl_easy *child, + bool exclusive) +{ + if(child->set.priority.parent) { + priority_remove_child(child->set.priority.parent, child); + } + + if(parent) { + struct Curl_data_prio_node **tail; + struct Curl_data_prio_node *pnode; + + pnode = calloc(1, sizeof(*pnode)); + if(!pnode) + return CURLE_OUT_OF_MEMORY; + pnode->data = child; + + if(parent->set.priority.children && exclusive) { + /* exclusive: move all existing children underneath the new child */ + struct Curl_data_prio_node *node = parent->set.priority.children; + while(node) { + node->data->set.priority.parent = child; + node = node->next; + } + + tail = &child->set.priority.children; + while(*tail) + tail = &(*tail)->next; + + DEBUGASSERT(!*tail); + *tail = parent->set.priority.children; + parent->set.priority.children = 0; + } + + tail = &parent->set.priority.children; + while(*tail) { + (*tail)->data->set.priority.exclusive = FALSE; + tail = &(*tail)->next; + } + + DEBUGASSERT(!*tail); + *tail = pnode; + } + + child->set.priority.parent = parent; + child->set.priority.exclusive = exclusive; + return CURLE_OK; +} + +#endif /* USE_NGHTTP2 */ + +#ifdef USE_NGHTTP2 +static void data_priority_cleanup(struct Curl_easy *data) +{ + while(data->set.priority.children) { + struct Curl_easy *tmp = data->set.priority.children->data; + priority_remove_child(data, tmp); + if(data->set.priority.parent) + Curl_data_priority_add_child(data->set.priority.parent, tmp, FALSE); + } + + if(data->set.priority.parent) + priority_remove_child(data->set.priority.parent, data); +} +#endif + +void Curl_data_priority_clear_state(struct Curl_easy *data) +{ + memset(&data->state.priority, 0, sizeof(data->state.priority)); +} + +#endif /* defined(USE_HTTP2) || defined(USE_HTTP3) */ diff --git a/Utilities/cmcurl/lib/url.h b/Utilities/cmcurl/lib/url.h new file mode 100644 index 0000000..7c1a29b --- /dev/null +++ b/Utilities/cmcurl/lib/url.h @@ -0,0 +1,81 @@ +#ifndef HEADER_CURL_URL_H +#define HEADER_CURL_URL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +/* + * Prototypes for library-wide functions provided by url.c + */ + +CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn); +CURLcode Curl_open(struct Curl_easy **curl); +CURLcode Curl_init_userdefined(struct Curl_easy *data); + +void Curl_freeset(struct Curl_easy *data); +CURLcode Curl_uc_to_curlcode(CURLUcode uc); +CURLcode Curl_close(struct Curl_easy **datap); /* opposite of curl_open() */ +CURLcode Curl_connect(struct Curl_easy *, bool *async, bool *protocol_connect); +void Curl_disconnect(struct Curl_easy *data, + struct connectdata *, bool dead_connection); +CURLcode Curl_setup_conn(struct Curl_easy *data, + bool *protocol_done); +void Curl_free_request_state(struct Curl_easy *data); +CURLcode Curl_parse_login_details(const char *login, const size_t len, + char **userptr, char **passwdptr, + char **optionsptr); + +/* Get protocol handler for a URI scheme + * @param scheme URI scheme, case-insensitive + * @return NULL of handler not found + */ +const struct Curl_handler *Curl_get_scheme_handler(const char *scheme); +const struct Curl_handler *Curl_getn_scheme_handler(const char *scheme, + size_t len); + +#define CURL_DEFAULT_PROXY_PORT 1080 /* default proxy port unless specified */ +#define CURL_DEFAULT_HTTPS_PROXY_PORT 443 /* default https proxy port unless + specified */ + +#ifdef CURL_DISABLE_VERBOSE_STRINGS +#define Curl_verboseconnect(x,y) Curl_nop_stmt +#else +void Curl_verboseconnect(struct Curl_easy *data, struct connectdata *conn); +#endif + +#if defined(USE_HTTP2) || defined(USE_HTTP3) +void Curl_data_priority_clear_state(struct Curl_easy *data); +#else +#define Curl_data_priority_clear_state(x) +#endif /* !(defined(USE_HTTP2) || defined(USE_HTTP3)) */ + +#ifdef USE_NGHTTP2 +CURLcode Curl_data_priority_add_child(struct Curl_easy *parent, + struct Curl_easy *child, + bool exclusive); +#else +#define Curl_data_priority_add_child(x, y, z) CURLE_NOT_BUILT_IN +#endif + +#endif /* HEADER_CURL_URL_H */ diff --git a/Utilities/cmcurl/lib/urlapi-int.h b/Utilities/cmcurl/lib/urlapi-int.h new file mode 100644 index 0000000..d6e240a --- /dev/null +++ b/Utilities/cmcurl/lib/urlapi-int.h @@ -0,0 +1,39 @@ +#ifndef HEADER_CURL_URLAPI_INT_H +#define HEADER_CURL_URLAPI_INT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, + bool guess_scheme); + +CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, + unsigned int flags); + +#ifdef DEBUGBUILD +CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, + bool has_scheme); +#endif + +#endif /* HEADER_CURL_URLAPI_INT_H */ diff --git a/Utilities/cmcurl/lib/urlapi.c b/Utilities/cmcurl/lib/urlapi.c new file mode 100644 index 0000000..0d11e48 --- /dev/null +++ b/Utilities/cmcurl/lib/urlapi.c @@ -0,0 +1,1953 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include "urlapi-int.h" +#include "strcase.h" +#include "url.h" +#include "escape.h" +#include "curl_ctype.h" +#include "inet_pton.h" +#include "inet_ntop.h" +#include "strdup.h" +#include "idn.h" +#include "curl_memrchr.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + /* MSDOS/Windows style drive prefix, eg c: in c:foo */ +#define STARTS_WITH_DRIVE_PREFIX(str) \ + ((('a' <= str[0] && str[0] <= 'z') || \ + ('A' <= str[0] && str[0] <= 'Z')) && \ + (str[1] == ':')) + + /* MSDOS/Windows style drive prefix, optionally with + * a '|' instead of ':', followed by a slash or NUL */ +#define STARTS_WITH_URL_DRIVE_PREFIX(str) \ + ((('a' <= (str)[0] && (str)[0] <= 'z') || \ + ('A' <= (str)[0] && (str)[0] <= 'Z')) && \ + ((str)[1] == ':' || (str)[1] == '|') && \ + ((str)[2] == '/' || (str)[2] == '\\' || (str)[2] == 0)) + +/* scheme is not URL encoded, the longest libcurl supported ones are... */ +#define MAX_SCHEME_LEN 40 + +/* + * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * sure we have _some_ value for AF_INET6 without polluting our fake value + * everywhere. + */ +#if !defined(ENABLE_IPV6) && !defined(AF_INET6) +#define AF_INET6 (AF_INET + 1) +#endif + +/* Internal representation of CURLU. Point to URL-encoded strings. */ +struct Curl_URL { + char *scheme; + char *user; + char *password; + char *options; /* IMAP only? */ + char *host; + char *zoneid; /* for numerical IPv6 addresses */ + char *port; + char *path; + char *query; + char *fragment; + long portnum; /* the numerical version */ +}; + +#define DEFAULT_SCHEME "https" + +static void free_urlhandle(struct Curl_URL *u) +{ + free(u->scheme); + free(u->user); + free(u->password); + free(u->options); + free(u->host); + free(u->zoneid); + free(u->port); + free(u->path); + free(u->query); + free(u->fragment); +} + +/* + * Find the separator at the end of the host name, or the '?' in cases like + * http://www.example.com?id=2380 + */ +static const char *find_host_sep(const char *url) +{ + const char *sep; + const char *query; + + /* Find the start of the hostname */ + sep = strstr(url, "//"); + if(!sep) + sep = url; + else + sep += 2; + + query = strchr(sep, '?'); + sep = strchr(sep, '/'); + + if(!sep) + sep = url + strlen(url); + + if(!query) + query = url + strlen(url); + + return sep < query ? sep : query; +} + +/* + * Decide whether a character in a URL must be escaped. + */ +#define urlchar_needs_escaping(c) (!(ISCNTRL(c) || ISSPACE(c) || ISGRAPH(c))) + +static const char hexdigits[] = "0123456789abcdef"; +/* urlencode_str() writes data into an output dynbuf and URL-encodes the + * spaces in the source URL accordingly. + * + * URL encoding should be skipped for host names, otherwise IDN resolution + * will fail. + */ +static CURLUcode urlencode_str(struct dynbuf *o, const char *url, + size_t len, bool relative, + bool query) +{ + /* we must add this with whitespace-replacing */ + bool left = !query; + const unsigned char *iptr; + const unsigned char *host_sep = (const unsigned char *) url; + + if(!relative) + host_sep = (const unsigned char *) find_host_sep(url); + + for(iptr = (unsigned char *)url; /* read from here */ + len; iptr++, len--) { + + if(iptr < host_sep) { + if(Curl_dyn_addn(o, iptr, 1)) + return CURLUE_OUT_OF_MEMORY; + continue; + } + + if(*iptr == ' ') { + if(left) { + if(Curl_dyn_addn(o, "%20", 3)) + return CURLUE_OUT_OF_MEMORY; + } + else { + if(Curl_dyn_addn(o, "+", 1)) + return CURLUE_OUT_OF_MEMORY; + } + continue; + } + + if(*iptr == '?') + left = FALSE; + + if(urlchar_needs_escaping(*iptr)) { + char out[3]={'%'}; + out[1] = hexdigits[*iptr>>4]; + out[2] = hexdigits[*iptr & 0xf]; + if(Curl_dyn_addn(o, out, 3)) + return CURLUE_OUT_OF_MEMORY; + } + else { + if(Curl_dyn_addn(o, iptr, 1)) + return CURLUE_OUT_OF_MEMORY; + } + } + + return CURLUE_OK; +} + +/* + * Returns the length of the scheme if the given URL is absolute (as opposed + * to relative). Stores the scheme in the buffer if TRUE and 'buf' is + * non-NULL. The buflen must be larger than MAX_SCHEME_LEN if buf is set. + * + * If 'guess_scheme' is TRUE, it means the URL might be provided without + * scheme. + */ +size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, + bool guess_scheme) +{ + int i = 0; + DEBUGASSERT(!buf || (buflen > MAX_SCHEME_LEN)); + (void)buflen; /* only used in debug-builds */ + if(buf) + buf[0] = 0; /* always leave a defined value in buf */ +#ifdef _WIN32 + if(guess_scheme && STARTS_WITH_DRIVE_PREFIX(url)) + return 0; +#endif + if(ISALPHA(url[0])) + for(i = 1; i < MAX_SCHEME_LEN; ++i) { + char s = url[i]; + if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.') )) { + /* RFC 3986 3.1 explains: + scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + */ + } + else { + break; + } + } + if(i && (url[i] == ':') && ((url[i + 1] == '/') || !guess_scheme)) { + /* If this does not guess scheme, the scheme always ends with the colon so + that this also detects data: URLs etc. In guessing mode, data: could + be the host name "data" with a specified port number. */ + + /* the length of the scheme is the name part only */ + size_t len = i; + if(buf) { + buf[i] = 0; + while(i--) { + buf[i] = Curl_raw_tolower(url[i]); + } + } + return len; + } + return 0; +} + +/* + * Concatenate a relative URL to a base URL making it absolute. + * URL-encodes any spaces. + * The returned pointer must be freed by the caller unless NULL + * (returns NULL on out of memory). + * + * Note that this function destroys the 'base' string. + */ +static char *concat_url(char *base, const char *relurl) +{ + /*** + TRY to append this new path to the old URL + to the right of the host part. Oh crap, this is doomed to cause + problems in the future... + */ + struct dynbuf newest; + char *protsep; + char *pathsep; + bool host_changed = FALSE; + const char *useurl = relurl; + + /* protsep points to the start of the host name */ + protsep = strstr(base, "//"); + if(!protsep) + protsep = base; + else + protsep += 2; /* pass the slashes */ + + if('/' != relurl[0]) { + int level = 0; + + /* First we need to find out if there's a ?-letter in the URL, + and cut it and the right-side of that off */ + pathsep = strchr(protsep, '?'); + if(pathsep) + *pathsep = 0; + + /* we have a relative path to append to the last slash if there's one + available, or if the new URL is just a query string (starts with a + '?') we append the new one at the end of the entire currently worked + out URL */ + if(useurl[0] != '?') { + pathsep = strrchr(protsep, '/'); + if(pathsep) + *pathsep = 0; + } + + /* Check if there's any slash after the host name, and if so, remember + that position instead */ + pathsep = strchr(protsep, '/'); + if(pathsep) + protsep = pathsep + 1; + else + protsep = NULL; + + /* now deal with one "./" or any amount of "../" in the newurl + and act accordingly */ + + if((useurl[0] == '.') && (useurl[1] == '/')) + useurl += 2; /* just skip the "./" */ + + while((useurl[0] == '.') && + (useurl[1] == '.') && + (useurl[2] == '/')) { + level++; + useurl += 3; /* pass the "../" */ + } + + if(protsep) { + while(level--) { + /* cut off one more level from the right of the original URL */ + pathsep = strrchr(protsep, '/'); + if(pathsep) + *pathsep = 0; + else { + *protsep = 0; + break; + } + } + } + } + else { + /* We got a new absolute path for this server */ + + if(relurl[1] == '/') { + /* the new URL starts with //, just keep the protocol part from the + original one */ + *protsep = 0; + useurl = &relurl[2]; /* we keep the slashes from the original, so we + skip the new ones */ + host_changed = TRUE; + } + else { + /* cut off the original URL from the first slash, or deal with URLs + without slash */ + pathsep = strchr(protsep, '/'); + if(pathsep) { + /* When people use badly formatted URLs, such as + "http://www.example.com?dir=/home/daniel" we must not use the first + slash, if there's a ?-letter before it! */ + char *sep = strchr(protsep, '?'); + if(sep && (sep < pathsep)) + pathsep = sep; + *pathsep = 0; + } + else { + /* There was no slash. Now, since we might be operating on a badly + formatted URL, such as "http://www.example.com?id=2380" which + doesn't use a slash separator as it is supposed to, we need to check + for a ?-letter as well! */ + pathsep = strchr(protsep, '?'); + if(pathsep) + *pathsep = 0; + } + } + } + + Curl_dyn_init(&newest, CURL_MAX_INPUT_LENGTH); + + /* copy over the root url part */ + if(Curl_dyn_add(&newest, base)) + return NULL; + + /* check if we need to append a slash */ + if(('/' == useurl[0]) || (protsep && !*protsep) || ('?' == useurl[0])) + ; + else { + if(Curl_dyn_addn(&newest, "/", 1)) + return NULL; + } + + /* then append the new piece on the right side */ + urlencode_str(&newest, useurl, strlen(useurl), !host_changed, FALSE); + + return Curl_dyn_ptr(&newest); +} + +/* scan for byte values <= 31, 127 and sometimes space */ +static CURLUcode junkscan(const char *url, size_t *urllen, unsigned int flags) +{ + static const char badbytes[]={ + /* */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x7f, 0x00 /* null-terminate */ + }; + size_t n = strlen(url); + size_t nfine; + + if(n > CURL_MAX_INPUT_LENGTH) + /* excessive input length */ + return CURLUE_MALFORMED_INPUT; + + nfine = strcspn(url, badbytes); + if((nfine != n) || + (!(flags & CURLU_ALLOW_SPACE) && strchr(url, ' '))) + return CURLUE_MALFORMED_INPUT; + + *urllen = n; + return CURLUE_OK; +} + +/* + * parse_hostname_login() + * + * Parse the login details (user name, password and options) from the URL and + * strip them out of the host name + * + */ +static CURLUcode parse_hostname_login(struct Curl_URL *u, + const char *login, + size_t len, + unsigned int flags, + size_t *offset) /* to the host name */ +{ + CURLUcode result = CURLUE_OK; + CURLcode ccode; + char *userp = NULL; + char *passwdp = NULL; + char *optionsp = NULL; + const struct Curl_handler *h = NULL; + + /* At this point, we assume all the other special cases have been taken + * care of, so the host is at most + * + * [user[:password][;options]]@]hostname + * + * We need somewhere to put the embedded details, so do that first. + */ + char *ptr; + + DEBUGASSERT(login); + + *offset = 0; + ptr = memchr(login, '@', len); + if(!ptr) + goto out; + + /* We will now try to extract the + * possible login information in a string like: + * ftp://user:password@ftp.my.site:8021/README */ + ptr++; + + /* if this is a known scheme, get some details */ + if(u->scheme) + h = Curl_get_scheme_handler(u->scheme); + + /* We could use the login information in the URL so extract it. Only parse + options if the handler says we should. Note that 'h' might be NULL! */ + ccode = Curl_parse_login_details(login, ptr - login - 1, + &userp, &passwdp, + (h && (h->flags & PROTOPT_URLOPTIONS)) ? + &optionsp:NULL); + if(ccode) { + result = CURLUE_BAD_LOGIN; + goto out; + } + + if(userp) { + if(flags & CURLU_DISALLOW_USER) { + /* Option DISALLOW_USER is set and url contains username. */ + result = CURLUE_USER_NOT_ALLOWED; + goto out; + } + free(u->user); + u->user = userp; + } + + if(passwdp) { + free(u->password); + u->password = passwdp; + } + + if(optionsp) { + free(u->options); + u->options = optionsp; + } + + /* the host name starts at this offset */ + *offset = ptr - login; + return CURLUE_OK; + +out: + + free(userp); + free(passwdp); + free(optionsp); + u->user = NULL; + u->password = NULL; + u->options = NULL; + + return result; +} + +UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, + bool has_scheme) +{ + char *portptr; + char *hostname = Curl_dyn_ptr(host); + /* + * Find the end of an IPv6 address on the ']' ending bracket. + */ + if(hostname[0] == '[') { + portptr = strchr(hostname, ']'); + if(!portptr) + return CURLUE_BAD_IPV6; + portptr++; + /* this is a RFC2732-style specified IP-address */ + if(*portptr) { + if(*portptr != ':') + return CURLUE_BAD_PORT_NUMBER; + } + else + portptr = NULL; + } + else + portptr = strchr(hostname, ':'); + + if(portptr) { + char *rest; + long port; + size_t keep = portptr - hostname; + + /* Browser behavior adaptation. If there's a colon with no digits after, + just cut off the name there which makes us ignore the colon and just + use the default port. Firefox, Chrome and Safari all do that. + + Don't do it if the URL has no scheme, to make something that looks like + a scheme not work! + */ + Curl_dyn_setlen(host, keep); + portptr++; + if(!*portptr) + return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER; + + if(!ISDIGIT(*portptr)) + return CURLUE_BAD_PORT_NUMBER; + + port = strtol(portptr, &rest, 10); /* Port number must be decimal */ + + if(port > 0xffff) + return CURLUE_BAD_PORT_NUMBER; + + if(rest[0]) + return CURLUE_BAD_PORT_NUMBER; + + u->portnum = port; + /* generate a new port number string to get rid of leading zeroes etc */ + free(u->port); + u->port = aprintf("%ld", port); + if(!u->port) + return CURLUE_OUT_OF_MEMORY; + } + + return CURLUE_OK; +} + +/* this assumes 'hostname' now starts with [ */ +static CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, + size_t hlen) /* length of hostname */ +{ + size_t len; + DEBUGASSERT(*hostname == '['); + if(hlen < 4) /* '[::]' is the shortest possible valid string */ + return CURLUE_BAD_IPV6; + hostname++; + hlen -= 2; + + /* only valid IPv6 letters are ok */ + len = strspn(hostname, "0123456789abcdefABCDEF:."); + + if(hlen != len) { + hlen = len; + if(hostname[len] == '%') { + /* this could now be '%[zone id]' */ + char zoneid[16]; + 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)) + zoneid[i++] = *h++; + if(!i || (']' != *h)) + return CURLUE_BAD_IPV6; + zoneid[i] = 0; + u->zoneid = strdup(zoneid); + if(!u->zoneid) + return CURLUE_OUT_OF_MEMORY; + hostname[len] = ']'; /* insert end bracket */ + hostname[len + 1] = 0; /* terminate the hostname */ + } + else + return CURLUE_BAD_IPV6; + /* hostname is fine */ + } + + /* Check the IPv6 address. */ + { + char dest[16]; /* fits a binary IPv6 address */ + char norm[MAX_IPADR_LEN]; + hostname[hlen] = 0; /* end the address there */ + if(1 != Curl_inet_pton(AF_INET6, hostname, dest)) + return CURLUE_BAD_IPV6; + + /* check if it can be done shorter */ + if(Curl_inet_ntop(AF_INET6, dest, norm, sizeof(norm)) && + (strlen(norm) < hlen)) { + strcpy(hostname, norm); + hlen = strlen(norm); + hostname[hlen + 1] = 0; + } + hostname[hlen] = ']'; /* restore ending bracket */ + } + return CURLUE_OK; +} + +static CURLUcode hostname_check(struct Curl_URL *u, char *hostname, + size_t hlen) /* length of hostname */ +{ + size_t len; + DEBUGASSERT(hostname); + + if(!hlen) + return CURLUE_NO_HOST; + else if(hostname[0] == '[') + return ipv6_parse(u, hostname, hlen); + else { + /* letters from the second string are not ok */ + len = strcspn(hostname, " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%"); + if(hlen != len) + /* hostname with bad content */ + return CURLUE_BAD_HOSTNAME; + } + return CURLUE_OK; +} + +/* + * Handle partial IPv4 numerical addresses and different bases, like + * '16843009', '0x7f', '0x7f.1' '0177.1.1.1' etc. + * + * If the given input string is syntactically wrong IPv4 or any part for + * example is too big, this function returns HOST_NAME. + * + * Output the "normalized" version of that input string in plain quad decimal + * integers. + * + * Returns the host type. + */ + +#define HOST_ERROR -1 /* out of memory */ +#define HOST_BAD -2 /* bad IPv4 address */ + +#define HOST_NAME 1 +#define HOST_IPV4 2 +#define HOST_IPV6 3 + +static int ipv4_normalize(struct dynbuf *host) +{ + bool done = FALSE; + int n = 0; + const char *c = Curl_dyn_ptr(host); + unsigned long parts[4] = {0, 0, 0, 0}; + CURLcode result = CURLE_OK; + + if(*c == '[') + return HOST_IPV6; + + while(!done) { + char *endp; + unsigned long l; + if(!ISDIGIT(*c)) + /* most importantly this doesn't allow a leading plus or minus */ + return HOST_NAME; + l = strtoul(c, &endp, 0); + + parts[n] = l; + c = endp; + + switch(*c) { + case '.': + if(n == 3) + return HOST_NAME; + n++; + c++; + break; + + case '\0': + done = TRUE; + break; + + default: + return HOST_NAME; + } + + /* overflow */ + if((l == ULONG_MAX) && (errno == ERANGE)) + return HOST_NAME; + +#if SIZEOF_LONG > 4 + /* a value larger than 32 bits */ + if(l > UINT_MAX) + return HOST_NAME; +#endif + } + + switch(n) { + case 0: /* a -- 32 bits */ + Curl_dyn_reset(host); + + result = Curl_dyn_addf(host, "%u.%u.%u.%u", + parts[0] >> 24, (parts[0] >> 16) & 0xff, + (parts[0] >> 8) & 0xff, parts[0] & 0xff); + break; + case 1: /* a.b -- 8.24 bits */ + if((parts[0] > 0xff) || (parts[1] > 0xffffff)) + return HOST_NAME; + Curl_dyn_reset(host); + result = Curl_dyn_addf(host, "%u.%u.%u.%u", + parts[0], (parts[1] >> 16) & 0xff, + (parts[1] >> 8) & 0xff, parts[1] & 0xff); + break; + case 2: /* a.b.c -- 8.8.16 bits */ + if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff)) + return HOST_NAME; + Curl_dyn_reset(host); + result = Curl_dyn_addf(host, "%u.%u.%u.%u", + parts[0], parts[1], (parts[2] >> 8) & 0xff, + parts[2] & 0xff); + break; + case 3: /* a.b.c.d -- 8.8.8.8 bits */ + if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) || + (parts[3] > 0xff)) + return HOST_NAME; + Curl_dyn_reset(host); + result = Curl_dyn_addf(host, "%u.%u.%u.%u", + parts[0], parts[1], parts[2], parts[3]); + break; + } + if(result) + return HOST_ERROR; + return HOST_IPV4; +} + +/* if necessary, replace the host content with a URL decoded version */ +static CURLUcode urldecode_host(struct dynbuf *host) +{ + char *per = NULL; + const char *hostname = Curl_dyn_ptr(host); + per = strchr(hostname, '%'); + if(!per) + /* nothing to decode */ + return CURLUE_OK; + else { + /* encoded */ + size_t dlen; + char *decoded; + CURLcode result = Curl_urldecode(hostname, 0, &decoded, &dlen, + REJECT_CTRL); + if(result) + return CURLUE_BAD_HOSTNAME; + Curl_dyn_reset(host); + result = Curl_dyn_addn(host, decoded, dlen); + free(decoded); + if(result) + return CURLUE_OUT_OF_MEMORY; + } + + return CURLUE_OK; +} + +static CURLUcode parse_authority(struct Curl_URL *u, + const char *auth, size_t authlen, + unsigned int flags, + struct dynbuf *host, + bool has_scheme) +{ + size_t offset; + CURLUcode result; + + /* + * Parse the login details and strip them out of the host name. + */ + result = parse_hostname_login(u, auth, authlen, flags, &offset); + if(result) + goto out; + + if(Curl_dyn_addn(host, auth + offset, authlen - offset)) { + result = CURLUE_OUT_OF_MEMORY; + goto out; + } + + result = Curl_parse_port(u, host, has_scheme); + if(result) + goto out; + + if(!Curl_dyn_len(host)) + return CURLUE_NO_HOST; + + switch(ipv4_normalize(host)) { + case HOST_IPV4: + break; + case HOST_IPV6: + result = ipv6_parse(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + break; + case HOST_NAME: + result = urldecode_host(host); + if(!result) + result = hostname_check(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + break; + case HOST_ERROR: + result = CURLUE_OUT_OF_MEMORY; + break; + case HOST_BAD: + default: + result = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */ + break; + } + +out: + return result; +} + +CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, + unsigned int flags) +{ + CURLUcode result; + struct dynbuf host; + + DEBUGASSERT(authority); + Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH); + + result = parse_authority(u, authority, strlen(authority), flags, + &host, !!u->scheme); + if(result) + Curl_dyn_free(&host); + else { + free(u->host); + u->host = Curl_dyn_ptr(&host); + } + return result; +} + +/* + * "Remove Dot Segments" + * https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4 + */ + +/* + * dedotdotify() + * @unittest: 1395 + * + * This function gets a null-terminated path with dot and dotdot sequences + * passed in and strips them off according to the rules in RFC 3986 section + * 5.2.4. + * + * The function handles a query part ('?' + stuff) appended but it expects + * that fragments ('#' + stuff) have already been cut off. + * + * RETURNS + * + * Zero for success and 'out' set to an allocated dedotdotified string. + */ +UNITTEST int dedotdotify(const char *input, size_t clen, char **outp); +UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) +{ + char *outptr; + const char *endp = &input[clen]; + char *out; + + *outp = NULL; + /* the path always starts with a slash, and a slash has not dot */ + if((clen < 2) || !memchr(input, '.', clen)) + return 0; + + out = malloc(clen + 1); + if(!out) + return 1; /* out of memory */ + + *out = 0; /* null-terminates, for inputs like "./" */ + outptr = out; + + do { + bool dotdot = TRUE; + if(*input == '.') { + /* A. If the input buffer begins with a prefix of "../" or "./", then + remove that prefix from the input buffer; otherwise, */ + + if(!strncmp("./", input, 2)) { + input += 2; + clen -= 2; + } + else if(!strncmp("../", input, 3)) { + input += 3; + clen -= 3; + } + /* D. if the input buffer consists only of "." or "..", then remove + that from the input buffer; otherwise, */ + + else if(!strcmp(".", input) || !strcmp("..", input) || + !strncmp(".?", input, 2) || !strncmp("..?", input, 3)) { + *out = 0; + break; + } + else + dotdot = FALSE; + } + else if(*input == '/') { + /* B. if the input buffer begins with a prefix of "/./" or "/.", where + "." is a complete path segment, then replace that prefix with "/" in + the input buffer; otherwise, */ + if(!strncmp("/./", input, 3)) { + input += 2; + clen -= 2; + } + else if(!strcmp("/.", input) || !strncmp("/.?", input, 3)) { + *outptr++ = '/'; + *outptr = 0; + break; + } + + /* C. if the input buffer begins with a prefix of "/../" or "/..", + where ".." is a complete path segment, then replace that prefix with + "/" in the input buffer and remove the last segment and its + preceding "/" (if any) from the output buffer; otherwise, */ + + else if(!strncmp("/../", input, 4)) { + input += 3; + clen -= 3; + /* remove the last segment from the output buffer */ + while(outptr > out) { + outptr--; + if(*outptr == '/') + break; + } + *outptr = 0; /* null-terminate where it stops */ + } + else if(!strcmp("/..", input) || !strncmp("/..?", input, 4)) { + /* remove the last segment from the output buffer */ + while(outptr > out) { + outptr--; + if(*outptr == '/') + break; + } + *outptr++ = '/'; + *outptr = 0; /* null-terminate where it stops */ + break; + } + else + dotdot = FALSE; + } + else + dotdot = FALSE; + + if(!dotdot) { + /* E. move the first path segment in the input buffer to the end of + the output buffer, including the initial "/" character (if any) and + any subsequent characters up to, but not including, the next "/" + character or the end of the input buffer. */ + + do { + *outptr++ = *input++; + clen--; + } while(*input && (*input != '/') && (*input != '?')); + *outptr = 0; + } + + /* continue until end of path */ + } while(input < endp); + + *outp = out; + return 0; /* success */ +} + +static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) +{ + const char *path; + size_t pathlen; + char *query = NULL; + char *fragment = NULL; + char schemebuf[MAX_SCHEME_LEN + 1]; + size_t schemelen = 0; + size_t urllen; + CURLUcode result = CURLUE_OK; + size_t fraglen = 0; + struct dynbuf host; + + DEBUGASSERT(url); + + Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH); + + result = junkscan(url, &urllen, flags); + if(result) + goto fail; + + schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf), + flags & (CURLU_GUESS_SCHEME| + CURLU_DEFAULT_SCHEME)); + + /* handle the file: scheme */ + if(schemelen && !strcmp(schemebuf, "file")) { + bool uncpath = FALSE; + if(urllen <= 6) { + /* file:/ is not enough to actually be a complete file: URL */ + result = CURLUE_BAD_FILE_URL; + goto fail; + } + + /* path has been allocated large enough to hold this */ + path = (char *)&url[5]; + pathlen = urllen - 5; + + u->scheme = strdup("file"); + if(!u->scheme) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + + /* Extra handling URLs with an authority component (i.e. that start with + * "file://") + * + * We allow omitted hostname (e.g. file:/<path>) -- valid according to + * RFC 8089, but not the (current) WHAT-WG URL spec. + */ + if(path[0] == '/' && path[1] == '/') { + /* swallow the two slashes */ + const char *ptr = &path[2]; + + /* + * According to RFC 8089, a file: URL can be reliably dereferenced if: + * + * o it has no/blank hostname, or + * + * o the hostname matches "localhost" (case-insensitively), or + * + * o the hostname is a FQDN that resolves to this machine, or + * + * o it is an UNC String transformed to an URI (Windows only, RFC 8089 + * Appendix E.3). + * + * For brevity, we only consider URLs with empty, "localhost", or + * "127.0.0.1" hostnames as local, otherwise as an UNC String. + * + * Additionally, there is an exception for URLs with a Windows drive + * letter in the authority (which was accidentally omitted from RFC 8089 + * Appendix E, but believe me, it was meant to be there. --MK) + */ + if(ptr[0] != '/' && !STARTS_WITH_URL_DRIVE_PREFIX(ptr)) { + /* the URL includes a host name, it must match "localhost" or + "127.0.0.1" to be valid */ + if(checkprefix("localhost/", ptr) || + checkprefix("127.0.0.1/", ptr)) { + ptr += 9; /* now points to the slash after the host */ + } + else { +#if defined(_WIN32) + size_t len; + + /* the host name, NetBIOS computer name, can not contain disallowed + chars, and the delimiting slash character must be appended to the + host name */ + path = strpbrk(ptr, "/\\:*?\"<>|"); + if(!path || *path != '/') { + result = CURLUE_BAD_FILE_URL; + goto fail; + } + + len = path - ptr; + if(len) { + if(Curl_dyn_addn(&host, ptr, len)) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + uncpath = TRUE; + } + + ptr -= 2; /* now points to the // before the host in UNC */ +#else + /* Invalid file://hostname/, expected localhost or 127.0.0.1 or + none */ + result = CURLUE_BAD_FILE_URL; + goto fail; +#endif + } + } + + path = ptr; + pathlen = urllen - (ptr - url); + } + + if(!uncpath) + /* no host for file: URLs by default */ + Curl_dyn_reset(&host); + +#if !defined(_WIN32) && !defined(MSDOS) && !defined(__CYGWIN__) + /* Don't allow Windows drive letters when not in Windows. + * This catches both "file:/c:" and "file:c:" */ + if(('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) || + STARTS_WITH_URL_DRIVE_PREFIX(path)) { + /* File drive letters are only accepted in MSDOS/Windows */ + result = CURLUE_BAD_FILE_URL; + goto fail; + } +#else + /* If the path starts with a slash and a drive letter, ditch the slash */ + if('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) { + /* This cannot be done with strcpy, as the memory chunks overlap! */ + path++; + pathlen--; + } +#endif + + } + else { + /* clear path */ + const char *schemep = NULL; + const char *hostp; + size_t hostlen; + + if(schemelen) { + int i = 0; + const char *p = &url[schemelen + 1]; + while((*p == '/') && (i < 4)) { + p++; + i++; + } + + schemep = schemebuf; + if(!Curl_get_scheme_handler(schemep) && + !(flags & CURLU_NON_SUPPORT_SCHEME)) { + result = CURLUE_UNSUPPORTED_SCHEME; + goto fail; + } + + if((i < 1) || (i > 3)) { + /* less than one or more than three slashes */ + result = CURLUE_BAD_SLASHES; + goto fail; + } + hostp = p; /* host name starts here */ + } + else { + /* no scheme! */ + + if(!(flags & (CURLU_DEFAULT_SCHEME|CURLU_GUESS_SCHEME))) { + result = CURLUE_BAD_SCHEME; + goto fail; + } + if(flags & CURLU_DEFAULT_SCHEME) + schemep = DEFAULT_SCHEME; + + /* + * The URL was badly formatted, let's try without scheme specified. + */ + hostp = url; + } + + if(schemep) { + u->scheme = strdup(schemep); + if(!u->scheme) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + } + + /* find the end of the host name + port number */ + hostlen = strcspn(hostp, "/?#"); + path = &hostp[hostlen]; + + /* this pathlen also contains the query and the fragment */ + pathlen = urllen - (path - url); + if(hostlen) { + + result = parse_authority(u, hostp, hostlen, flags, &host, schemelen); + if(result) + goto fail; + + if((flags & CURLU_GUESS_SCHEME) && !schemep) { + const char *hostname = Curl_dyn_ptr(&host); + /* legacy curl-style guess based on host name */ + if(checkprefix("ftp.", hostname)) + schemep = "ftp"; + else if(checkprefix("dict.", hostname)) + schemep = "dict"; + else if(checkprefix("ldap.", hostname)) + schemep = "ldap"; + else if(checkprefix("imap.", hostname)) + schemep = "imap"; + else if(checkprefix("smtp.", hostname)) + schemep = "smtp"; + else if(checkprefix("pop3.", hostname)) + schemep = "pop3"; + else + schemep = "http"; + + u->scheme = strdup(schemep); + if(!u->scheme) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + } + } + else if(flags & CURLU_NO_AUTHORITY) { + /* allowed to be empty. */ + if(Curl_dyn_add(&host, "")) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + } + else { + result = CURLUE_NO_HOST; + goto fail; + } + } + + fragment = strchr(path, '#'); + if(fragment) { + fraglen = pathlen - (fragment - path); + if(fraglen > 1) { + /* skip the leading '#' in the copy but include the terminating null */ + if(flags & CURLU_URLENCODE) { + struct dynbuf enc; + Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + if(urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, FALSE)) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + u->fragment = Curl_dyn_ptr(&enc); + } + else { + u->fragment = Curl_strndup(fragment + 1, fraglen - 1); + if(!u->fragment) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + } + } + /* after this, pathlen still contains the query */ + pathlen -= fraglen; + } + + DEBUGASSERT(pathlen < urllen); + query = memchr(path, '?', pathlen); + if(query) { + size_t qlen = fragment ? (size_t)(fragment - query) : + pathlen - (query - path); + pathlen -= qlen; + if(qlen > 1) { + if(flags & CURLU_URLENCODE) { + struct dynbuf enc; + Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + /* skip the leading question mark */ + if(urlencode_str(&enc, query + 1, qlen - 1, TRUE, TRUE)) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + u->query = Curl_dyn_ptr(&enc); + } + else { + u->query = Curl_strndup(query + 1, qlen - 1); + if(!u->query) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + } + } + else { + /* single byte query */ + u->query = strdup(""); + if(!u->query) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + } + } + + if(pathlen && (flags & CURLU_URLENCODE)) { + struct dynbuf enc; + Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + if(urlencode_str(&enc, path, pathlen, TRUE, FALSE)) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + pathlen = Curl_dyn_len(&enc); + path = u->path = Curl_dyn_ptr(&enc); + } + + if(pathlen <= 1) { + /* there is no path left or just the slash, unset */ + path = NULL; + } + else { + if(!u->path) { + u->path = Curl_strndup(path, pathlen); + if(!u->path) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + path = u->path; + } + else if(flags & CURLU_URLENCODE) + /* it might have encoded more than just the path so cut it */ + u->path[pathlen] = 0; + + if(!(flags & CURLU_PATH_AS_IS)) { + /* remove ../ and ./ sequences according to RFC3986 */ + char *dedot; + int err = dedotdotify((char *)path, pathlen, &dedot); + if(err) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + if(dedot) { + free(u->path); + u->path = dedot; + } + } + } + + u->host = Curl_dyn_ptr(&host); + + return result; +fail: + Curl_dyn_free(&host); + free_urlhandle(u); + return result; +} + +/* + * Parse the URL and, if successful, replace everything in the Curl_URL struct. + */ +static CURLUcode parseurl_and_replace(const char *url, CURLU *u, + unsigned int flags) +{ + CURLUcode result; + CURLU tmpurl; + memset(&tmpurl, 0, sizeof(tmpurl)); + result = parseurl(url, &tmpurl, flags); + if(!result) { + free_urlhandle(u); + *u = tmpurl; + } + return result; +} + +/* + */ +CURLU *curl_url(void) +{ + return calloc(1, sizeof(struct Curl_URL)); +} + +void curl_url_cleanup(CURLU *u) +{ + if(u) { + free_urlhandle(u); + free(u); + } +} + +#define DUP(dest, src, name) \ + do { \ + if(src->name) { \ + dest->name = strdup(src->name); \ + if(!dest->name) \ + goto fail; \ + } \ + } while(0) + +CURLU *curl_url_dup(const CURLU *in) +{ + struct Curl_URL *u = calloc(1, sizeof(struct Curl_URL)); + if(u) { + DUP(u, in, scheme); + DUP(u, in, user); + 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; + } + return u; +fail: + curl_url_cleanup(u); + return NULL; +} + +CURLUcode curl_url_get(const CURLU *u, CURLUPart what, + char **part, unsigned int flags) +{ + const char *ptr; + CURLUcode ifmissing = CURLUE_UNKNOWN_PART; + char portbuf[7]; + bool urldecode = (flags & CURLU_URLDECODE)?1:0; + bool urlencode = (flags & CURLU_URLENCODE)?1:0; + bool punycode = FALSE; + bool depunyfy = FALSE; + bool plusdecode = FALSE; + (void)flags; + if(!u) + return CURLUE_BAD_HANDLE; + if(!part) + return CURLUE_BAD_PARTPOINTER; + *part = NULL; + + switch(what) { + case CURLUPART_SCHEME: + ptr = u->scheme; + ifmissing = CURLUE_NO_SCHEME; + urldecode = FALSE; /* never for schemes */ + break; + case CURLUPART_USER: + ptr = u->user; + ifmissing = CURLUE_NO_USER; + break; + case CURLUPART_PASSWORD: + ptr = u->password; + ifmissing = CURLUE_NO_PASSWORD; + break; + case CURLUPART_OPTIONS: + ptr = u->options; + ifmissing = CURLUE_NO_OPTIONS; + break; + case CURLUPART_HOST: + ptr = u->host; + ifmissing = CURLUE_NO_HOST; + punycode = (flags & CURLU_PUNYCODE)?1:0; + depunyfy = (flags & CURLU_PUNY2IDN)?1:0; + break; + case CURLUPART_ZONEID: + ptr = u->zoneid; + ifmissing = CURLUE_NO_ZONEID; + break; + case CURLUPART_PORT: + ptr = u->port; + ifmissing = CURLUE_NO_PORT; + urldecode = FALSE; /* never for port */ + if(!ptr && (flags & CURLU_DEFAULT_PORT) && u->scheme) { + /* there's no stored port number, but asked to deliver + a default one for the scheme */ + const struct Curl_handler *h = Curl_get_scheme_handler(u->scheme); + if(h) { + msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); + 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_handler *h = Curl_get_scheme_handler(u->scheme); + if(h && (h->defport == u->portnum) && + (flags & CURLU_NO_DEFAULT_PORT)) + ptr = NULL; + } + break; + case CURLUPART_PATH: + ptr = u->path; + if(!ptr) + ptr = "/"; + break; + case CURLUPART_QUERY: + ptr = u->query; + ifmissing = CURLUE_NO_QUERY; + plusdecode = urldecode; + break; + case CURLUPART_FRAGMENT: + ptr = u->fragment; + ifmissing = CURLUE_NO_FRAGMENT; + break; + case CURLUPART_URL: { + char *url; + char *scheme; + char *options = u->options; + char *port = u->port; + char *allochost = NULL; + punycode = (flags & CURLU_PUNYCODE)?1:0; + depunyfy = (flags & CURLU_PUNY2IDN)?1:0; + if(u->scheme && strcasecompare("file", u->scheme)) { + url = aprintf("file://%s%s%s", + u->path, + u->fragment? "#": "", + u->fragment? u->fragment : ""); + } + else if(!u->host) + return CURLUE_NO_HOST; + else { + const struct Curl_handler *h = NULL; + if(u->scheme) + scheme = u->scheme; + else if(flags & CURLU_DEFAULT_SCHEME) + scheme = (char *) DEFAULT_SCHEME; + else + return CURLUE_NO_SCHEME; + + h = Curl_get_scheme_handler(scheme); + if(!port && (flags & CURLU_DEFAULT_PORT)) { + /* there's no stored port number, but asked to deliver + a default one for the scheme */ + if(h) { + msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); + port = portbuf; + } + } + else if(port) { + /* there is a stored port number, but asked to inhibit if it matches + the default one for the scheme */ + if(h && (h->defport == u->portnum) && + (flags & CURLU_NO_DEFAULT_PORT)) + port = NULL; + } + + if(h && !(h->flags & PROTOPT_URLOPTIONS)) + options = NULL; + + if(u->host[0] == '[') { + if(u->zoneid) { + /* make it '[ host %25 zoneid ]' */ + struct dynbuf enc; + size_t hostlen = strlen(u->host); + Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + if(Curl_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host, + u->zoneid)) + return CURLUE_OUT_OF_MEMORY; + allochost = Curl_dyn_ptr(&enc); + } + } + else if(urlencode) { + allochost = curl_easy_escape(NULL, u->host, 0); + if(!allochost) + return CURLUE_OUT_OF_MEMORY; + } + else if(punycode) { + if(!Curl_is_ASCII_name(u->host)) { +#ifndef USE_IDN + return CURLUE_LACKS_IDN; +#else + CURLcode result = Curl_idn_decode(u->host, &allochost); + if(result) + return (result == CURLE_OUT_OF_MEMORY) ? + CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME; +#endif + } + } + else if(depunyfy) { + if(Curl_is_ASCII_name(u->host) && !strncmp("xn--", u->host, 4)) { +#ifndef USE_IDN + return CURLUE_LACKS_IDN; +#else + CURLcode result = Curl_idn_encode(u->host, &allochost); + if(result) + /* this is the most likely error */ + return (result == CURLE_OUT_OF_MEMORY) ? + CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME; +#endif + } + } + + url = aprintf("%s://%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + scheme, + u->user ? u->user : "", + u->password ? ":": "", + u->password ? u->password : "", + options ? ";" : "", + options ? options : "", + (u->user || u->password || options) ? "@": "", + allochost ? allochost : u->host, + port ? ":": "", + port ? port : "", + u->path ? u->path : "/", + (u->query && u->query[0]) ? "?": "", + (u->query && u->query[0]) ? u->query : "", + u->fragment? "#": "", + u->fragment? u->fragment : ""); + free(allochost); + } + if(!url) + return CURLUE_OUT_OF_MEMORY; + *part = url; + return CURLUE_OK; + } + default: + ptr = NULL; + break; + } + if(ptr) { + size_t partlen = strlen(ptr); + size_t i = 0; + *part = Curl_strndup(ptr, partlen); + if(!*part) + return CURLUE_OUT_OF_MEMORY; + if(plusdecode) { + /* convert + to space */ + char *plus = *part; + for(i = 0; i < partlen; ++plus, i++) { + if(*plus == '+') + *plus = ' '; + } + } + if(urldecode) { + char *decoded; + size_t dlen; + /* this unconditional rejection of control bytes is documented + API behavior */ + CURLcode res = Curl_urldecode(*part, 0, &decoded, &dlen, REJECT_CTRL); + free(*part); + if(res) { + *part = NULL; + return CURLUE_URLDECODE; + } + *part = decoded; + partlen = dlen; + } + if(urlencode) { + struct dynbuf enc; + Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + if(urlencode_str(&enc, *part, partlen, TRUE, + what == CURLUPART_QUERY)) + return CURLUE_OUT_OF_MEMORY; + free(*part); + *part = Curl_dyn_ptr(&enc); + } + else if(punycode) { + if(!Curl_is_ASCII_name(u->host)) { +#ifndef USE_IDN + return CURLUE_LACKS_IDN; +#else + char *allochost; + CURLcode result = Curl_idn_decode(*part, &allochost); + if(result) + return (result == CURLE_OUT_OF_MEMORY) ? + CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME; + free(*part); + *part = allochost; +#endif + } + } + else if(depunyfy) { + if(Curl_is_ASCII_name(u->host) && !strncmp("xn--", u->host, 4)) { +#ifndef USE_IDN + return CURLUE_LACKS_IDN; +#else + char *allochost; + CURLcode result = Curl_idn_encode(*part, &allochost); + if(result) + return (result == CURLE_OUT_OF_MEMORY) ? + CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME; + free(*part); + *part = allochost; +#endif + } + } + + return CURLUE_OK; + } + else + return ifmissing; +} + +CURLUcode curl_url_set(CURLU *u, CURLUPart what, + const char *part, unsigned int flags) +{ + char **storep = NULL; + long port = 0; + bool urlencode = (flags & CURLU_URLENCODE)? 1 : 0; + bool plusencode = FALSE; + bool urlskipslash = FALSE; + bool leadingslash = FALSE; + bool appendquery = FALSE; + bool equalsencode = FALSE; + size_t nalloc; + + if(!u) + return CURLUE_BAD_HANDLE; + if(!part) { + /* setting a part to NULL clears it */ + switch(what) { + case CURLUPART_URL: + break; + case CURLUPART_SCHEME: + storep = &u->scheme; + break; + case CURLUPART_USER: + storep = &u->user; + break; + case CURLUPART_PASSWORD: + storep = &u->password; + break; + case CURLUPART_OPTIONS: + storep = &u->options; + break; + case CURLUPART_HOST: + storep = &u->host; + break; + case CURLUPART_ZONEID: + storep = &u->zoneid; + break; + case CURLUPART_PORT: + u->portnum = 0; + storep = &u->port; + break; + case CURLUPART_PATH: + storep = &u->path; + break; + case CURLUPART_QUERY: + storep = &u->query; + break; + case CURLUPART_FRAGMENT: + storep = &u->fragment; + break; + default: + return CURLUE_UNKNOWN_PART; + } + if(storep && *storep) { + Curl_safefree(*storep); + } + else if(!storep) { + free_urlhandle(u); + memset(u, 0, sizeof(struct Curl_URL)); + } + return CURLUE_OK; + } + + nalloc = strlen(part); + if(nalloc > CURL_MAX_INPUT_LENGTH) + /* excessive input length */ + return CURLUE_MALFORMED_INPUT; + + switch(what) { + case CURLUPART_SCHEME: { + size_t plen = strlen(part); + const char *s = part; + if((plen > MAX_SCHEME_LEN) || (plen < 1)) + /* too long or too short */ + return CURLUE_BAD_SCHEME; + /* verify that it is a fine scheme */ + if(!(flags & CURLU_NON_SUPPORT_SCHEME) && !Curl_get_scheme_handler(part)) + return CURLUE_UNSUPPORTED_SCHEME; + storep = &u->scheme; + urlencode = FALSE; /* never */ + if(ISALPHA(*s)) { + /* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ + while(--plen) { + if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.')) + s++; /* fine */ + else + return CURLUE_BAD_SCHEME; + } + } + else + return CURLUE_BAD_SCHEME; + break; + } + case CURLUPART_USER: + storep = &u->user; + break; + case CURLUPART_PASSWORD: + storep = &u->password; + break; + case CURLUPART_OPTIONS: + storep = &u->options; + break; + case CURLUPART_HOST: + storep = &u->host; + Curl_safefree(u->zoneid); + break; + case CURLUPART_ZONEID: + storep = &u->zoneid; + break; + case CURLUPART_PORT: + { + char *endp; + urlencode = FALSE; /* never */ + port = strtol(part, &endp, 10); /* Port number must be decimal */ + if((port <= 0) || (port > 0xffff)) + return CURLUE_BAD_PORT_NUMBER; + if(*endp) + /* weirdly provided number, not good! */ + return CURLUE_BAD_PORT_NUMBER; + storep = &u->port; + } + break; + case CURLUPART_PATH: + urlskipslash = TRUE; + leadingslash = TRUE; /* enforce */ + storep = &u->path; + break; + case CURLUPART_QUERY: + plusencode = urlencode; + appendquery = (flags & CURLU_APPENDQUERY)?1:0; + equalsencode = appendquery; + storep = &u->query; + break; + case CURLUPART_FRAGMENT: + storep = &u->fragment; + break; + case CURLUPART_URL: { + /* + * Allow a new URL to replace the existing (if any) contents. + * + * If the existing contents is enough for a URL, allow a relative URL to + * replace it. + */ + CURLUcode result; + char *oldurl; + char *redired_url; + + if(!nalloc) + /* a blank URL is not a valid URL */ + return CURLUE_MALFORMED_INPUT; + + /* if the new thing is absolute or the old one is not + * (we could not get an absolute url in 'oldurl'), + * then replace the existing with the new. */ + if(Curl_is_absolute_url(part, NULL, 0, + flags & (CURLU_GUESS_SCHEME| + CURLU_DEFAULT_SCHEME)) + || curl_url_get(u, CURLUPART_URL, &oldurl, flags)) { + return parseurl_and_replace(part, u, flags); + } + + /* apply the relative part to create a new URL + * and replace the existing one with it. */ + redired_url = concat_url(oldurl, part); + free(oldurl); + if(!redired_url) + return CURLUE_OUT_OF_MEMORY; + + result = parseurl_and_replace(redired_url, u, flags); + free(redired_url); + return result; + } + default: + return CURLUE_UNKNOWN_PART; + } + DEBUGASSERT(storep); + { + const char *newp; + struct dynbuf enc; + Curl_dyn_init(&enc, nalloc * 3 + 1 + leadingslash); + + if(leadingslash && (part[0] != '/')) { + CURLcode result = Curl_dyn_addn(&enc, "/", 1); + if(result) + return CURLUE_OUT_OF_MEMORY; + } + if(urlencode) { + const unsigned char *i; + + for(i = (const unsigned char *)part; *i; i++) { + CURLcode result; + if((*i == ' ') && plusencode) { + result = Curl_dyn_addn(&enc, "+", 1); + if(result) + return CURLUE_OUT_OF_MEMORY; + } + else if(ISUNRESERVED(*i) || + ((*i == '/') && urlskipslash) || + ((*i == '=') && equalsencode)) { + if((*i == '=') && equalsencode) + /* only skip the first equals sign */ + equalsencode = FALSE; + result = Curl_dyn_addn(&enc, i, 1); + if(result) + return CURLUE_OUT_OF_MEMORY; + } + else { + char out[3]={'%'}; + out[1] = hexdigits[*i>>4]; + out[2] = hexdigits[*i & 0xf]; + result = Curl_dyn_addn(&enc, out, 3); + if(result) + return CURLUE_OUT_OF_MEMORY; + } + } + } + else { + char *p; + CURLcode result = Curl_dyn_add(&enc, part); + if(result) + return CURLUE_OUT_OF_MEMORY; + p = Curl_dyn_ptr(&enc); + while(*p) { + /* make sure percent encoded are lower case */ + if((*p == '%') && ISXDIGIT(p[1]) && ISXDIGIT(p[2]) && + (ISUPPER(p[1]) || ISUPPER(p[2]))) { + p[1] = Curl_raw_tolower(p[1]); + p[2] = Curl_raw_tolower(p[2]); + p += 3; + } + else + p++; + } + } + newp = Curl_dyn_ptr(&enc); + + if(appendquery && newp) { + /* Append the 'newp' string onto the old query. Add a '&' separator if + none is present at the end of the existing query already */ + + size_t querylen = u->query ? strlen(u->query) : 0; + bool addamperand = querylen && (u->query[querylen -1] != '&'); + if(querylen) { + struct dynbuf qbuf; + Curl_dyn_init(&qbuf, CURL_MAX_INPUT_LENGTH); + + if(Curl_dyn_addn(&qbuf, u->query, querylen)) /* add original query */ + goto nomem; + + if(addamperand) { + if(Curl_dyn_addn(&qbuf, "&", 1)) + goto nomem; + } + if(Curl_dyn_add(&qbuf, newp)) + goto nomem; + Curl_dyn_free(&enc); + free(*storep); + *storep = Curl_dyn_ptr(&qbuf); + return CURLUE_OK; +nomem: + Curl_dyn_free(&enc); + return CURLUE_OUT_OF_MEMORY; + } + } + + else if(what == CURLUPART_HOST) { + size_t n = Curl_dyn_len(&enc); + if(!n && (flags & CURLU_NO_AUTHORITY)) { + /* Skip hostname check, it's allowed to be empty. */ + } + else { + if(!n || hostname_check(u, (char *)newp, n)) { + Curl_dyn_free(&enc); + return CURLUE_BAD_HOSTNAME; + } + } + } + + free(*storep); + *storep = (char *)newp; + } + /* set after the string, to make it not assigned if the allocation above + fails */ + if(port) + u->portnum = port; + return CURLUE_OK; +} diff --git a/Utilities/cmcurl/lib/urldata.h b/Utilities/cmcurl/lib/urldata.h new file mode 100644 index 0000000..ff66148 --- /dev/null +++ b/Utilities/cmcurl/lib/urldata.h @@ -0,0 +1,2038 @@ +#ifndef HEADER_CURL_URLDATA_H +#define HEADER_CURL_URLDATA_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* This file is for lib internal stuff */ + +#include "curl_setup.h" + +#define PORT_FTP 21 +#define PORT_FTPS 990 +#define PORT_TELNET 23 +#define PORT_HTTP 80 +#define PORT_HTTPS 443 +#define PORT_DICT 2628 +#define PORT_LDAP 389 +#define PORT_LDAPS 636 +#define PORT_TFTP 69 +#define PORT_SSH 22 +#define PORT_IMAP 143 +#define PORT_IMAPS 993 +#define PORT_POP3 110 +#define PORT_POP3S 995 +#define PORT_SMB 445 +#define PORT_SMBS 445 +#define PORT_SMTP 25 +#define PORT_SMTPS 465 /* sometimes called SSMTP */ +#define PORT_RTSP 554 +#define PORT_RTMP 1935 +#define PORT_RTMPT PORT_HTTP +#define PORT_RTMPS PORT_HTTPS +#define PORT_GOPHER 70 +#define PORT_MQTT 1883 + +#ifdef USE_WEBSOCKETS +/* CURLPROTO_GOPHERS (29) is the highest publicly used protocol bit number, + * the rest are internal information. If we use higher bits we only do this on + * platforms that have a >= 64 bit type and then we use such a type for the + * protocol fields in the protocol handler. + */ +#define CURLPROTO_WS (1<<30) +#define CURLPROTO_WSS ((curl_prot_t)1<<31) +#else +#define CURLPROTO_WS 0 +#define CURLPROTO_WSS 0 +#endif + +/* This should be undefined once we need bit 32 or higher */ +#define PROTO_TYPE_SMALL + +#ifndef PROTO_TYPE_SMALL +typedef curl_off_t curl_prot_t; +#else +typedef unsigned int curl_prot_t; +#endif + +/* This mask is for all the old protocols that are provided and defined in the + public header and shall exclude protocols added since which are not exposed + in the API */ +#define CURLPROTO_MASK (0x3ffffff) + +#define DICT_MATCH "/MATCH:" +#define DICT_MATCH2 "/M:" +#define DICT_MATCH3 "/FIND:" +#define DICT_DEFINE "/DEFINE:" +#define DICT_DEFINE2 "/D:" +#define DICT_DEFINE3 "/LOOKUP:" + +#define CURL_DEFAULT_USER "anonymous" +#define CURL_DEFAULT_PASSWORD "ftp@example.com" + +/* Convenience defines for checking protocols or their SSL based version. Each + protocol handler should only ever have a single CURLPROTO_ in its protocol + field. */ +#define PROTO_FAMILY_HTTP (CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_WS| \ + CURLPROTO_WSS) +#define PROTO_FAMILY_FTP (CURLPROTO_FTP|CURLPROTO_FTPS) +#define PROTO_FAMILY_POP3 (CURLPROTO_POP3|CURLPROTO_POP3S) +#define PROTO_FAMILY_SMB (CURLPROTO_SMB|CURLPROTO_SMBS) +#define PROTO_FAMILY_SMTP (CURLPROTO_SMTP|CURLPROTO_SMTPS) +#define PROTO_FAMILY_SSH (CURLPROTO_SCP|CURLPROTO_SFTP) + +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) || \ + !defined(CURL_DISABLE_POP3) +/* these protocols support CURLOPT_DIRLISTONLY */ +#define CURL_LIST_ONLY_PROTOCOL 1 +#endif + +#define DEFAULT_CONNCACHE_SIZE 5 + +/* length of longest IPv6 address string including the trailing null */ +#define MAX_IPADR_LEN sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") + +/* Default FTP/IMAP etc response timeout in milliseconds */ +#define RESP_TIMEOUT (120*1000) + +/* Max string input length is a precaution against abuse and to detect junk + input easier and better. */ +#define CURL_MAX_INPUT_LENGTH 8000000 + + +#include "cookie.h" +#include "psl.h" +#include "formdata.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN6_H +#include <netinet/in6.h> +#endif + +#include "timeval.h" + +#include <curl/curl.h> + +#include "http_chunks.h" /* for the structs and enum stuff */ +#include "hostip.h" +#include "hash.h" +#include "splay.h" +#include "dynbuf.h" +#include "dynhds.h" + +/* return the count of bytes sent, or -1 on error */ +typedef ssize_t (Curl_send)(struct Curl_easy *data, /* transfer */ + int sockindex, /* socketindex */ + const void *buf, /* data to write */ + size_t len, /* max amount to write */ + CURLcode *err); /* error to return */ + +/* return the count of bytes read, or -1 on error */ +typedef ssize_t (Curl_recv)(struct Curl_easy *data, /* transfer */ + int sockindex, /* socketindex */ + char *buf, /* store data here */ + size_t len, /* max amount to read */ + CURLcode *err); /* error to return */ + +#ifdef USE_HYPER +typedef CURLcode (*Curl_datastream)(struct Curl_easy *data, + struct connectdata *conn, + int *didwhat, + bool *done, + int select_res); +#endif + +#include "mime.h" +#include "imap.h" +#include "pop3.h" +#include "smtp.h" +#include "ftp.h" +#include "file.h" +#include "vssh/ssh.h" +#include "http.h" +#include "rtsp.h" +#include "smb.h" +#include "mqtt.h" +#include "ftplistparser.h" +#include "multihandle.h" +#include "c-hyper.h" +#include "cf-socket.h" + +#ifdef HAVE_GSSAPI +# ifdef HAVE_GSSGNU +# include <gss.h> +# elif defined HAVE_GSSAPI_GSSAPI_H +# include <gssapi/gssapi.h> +# else +# include <gssapi.h> +# endif +# ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H +# include <gssapi/gssapi_generic.h> +# endif +#endif + +#ifdef USE_LIBSSH2 +#include <libssh2.h> +#include <libssh2_sftp.h> +#endif /* USE_LIBSSH2 */ + +#define READBUFFER_SIZE CURL_MAX_WRITE_SIZE +#define READBUFFER_MAX CURL_MAX_READ_SIZE +#define READBUFFER_MIN 1024 + +/* The default upload buffer size, should not be smaller than + CURL_MAX_WRITE_SIZE, as it needs to hold a full buffer as could be sent in + a write callback. + + The size was 16KB for many years but was bumped to 64KB because it makes + libcurl able to do significantly faster uploads in some circumstances. Even + larger buffers can help further, but this is deemed a fair memory/speed + compromise. */ +#define UPLOADBUFFER_DEFAULT 65536 +#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 HAVE_GSSAPI +/* Types needed for krb5-ftp connections */ +struct krb5buffer { + void *data; + size_t size; + size_t index; + BIT(eof_flag); +}; + +enum protection_level { + PROT_NONE, /* first in list */ + PROT_CLEAR, + PROT_SAFE, + PROT_CONFIDENTIAL, + PROT_PRIVATE, + PROT_CMD, + PROT_LAST /* last in list */ +}; +#endif + +/* enum for the nonblocking SSL connection state machine */ +typedef enum { + ssl_connect_1, + ssl_connect_2, + ssl_connect_2_reading, + ssl_connect_2_writing, + ssl_connect_3, + ssl_connect_done +} ssl_connect_state; + +typedef enum { + ssl_connection_none, + ssl_connection_negotiating, + ssl_connection_complete +} ssl_connection_state; + +/* SSL backend-specific data; declared differently by each SSL backend */ +struct ssl_backend_data; + +struct ssl_peer { + char *hostname; /* hostname for verification */ + char *dispname; /* display version of hostname */ + char *sni; /* SNI version of hostname or NULL if not usable */ + BIT(is_ip_address); /* if hostname is an IPv4|6 address */ +}; + +struct ssl_primary_config { + char *CApath; /* certificate dir (doesn't work on windows) */ + char *CAfile; /* certificate to verify peer against */ + char *issuercert; /* optional issuer certificate filename */ + char *clientcert; + char *cipher_list; /* list of ciphers to use */ + char *cipher_list13; /* list of TLS 1.3 cipher suites to use */ + char *pinned_key; + char *CRLfile; /* CRL to check certificate revocation */ + struct curl_blob *cert_blob; + struct curl_blob *ca_info_blob; + struct curl_blob *issuercert_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 */ + unsigned char ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ + unsigned int version_max; /* max supported version the client wants to use */ + unsigned char version; /* what version the client wants to use */ + 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(sessionid); /* cache session IDs or not */ +}; + +struct ssl_config_data { + struct ssl_primary_config primary; + long certverifyresult; /* result from the certificate verification */ + curl_ssl_ctx_callback fsslctx; /* function to initialize ssl ctx */ + void *fsslctxp; /* parameter for call back */ + char *cert_type; /* format for certificate (default: PEM)*/ + char *key; /* private key file name */ + struct curl_blob *key_blob; + char *key_type; /* format for private key (default: PEM) */ + char *key_passwd; /* plain text private key password */ + BIT(certinfo); /* gather lots of certificate info */ + BIT(falsestart); + BIT(enable_beast); /* allow this flaw for interoperability's sake */ + BIT(no_revoke); /* disable SSL certificate revocation checks */ + BIT(no_partialchain); /* don't 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) */ +}; + +struct ssl_general_config { + size_t max_ssl_sessions; /* SSL session id cache size */ + int ca_cache_timeout; /* Certificate store cache timeout (seconds) */ +}; + +/* information stored about one single SSL session */ +struct Curl_ssl_session { + char *name; /* host name for which this ID was used */ + char *conn_to_host; /* host name for the connection (may be NULL) */ + const char *scheme; /* protocol scheme used */ + void *sessionid; /* as returned from the SSL layer */ + size_t idsize; /* if known, otherwise 0 */ + long age; /* just a number, the higher the more recent */ + int remote_port; /* remote port */ + int conn_to_port; /* remote port for the connection (may be -1) */ + struct ssl_primary_config ssl_config; /* setup for this session */ +}; + +#ifdef USE_WINDOWS_SSPI +#include "curl_sspi.h" +#endif + +#ifndef CURL_DISABLE_DIGEST_AUTH +/* Struct used for Digest challenge-response authentication */ +struct digestdata { +#if defined(USE_WINDOWS_SSPI) + BYTE *input_token; + size_t input_token_len; + CtxtHandle *http_context; + /* copy of user/passwd used to make the identity for http_context. + either may be NULL. */ + char *user; + char *passwd; +#else + char *nonce; + char *cnonce; + char *realm; + char *opaque; + char *qop; + char *algorithm; + int nc; /* nonce count */ + unsigned char algo; + BIT(stale); /* set true for re-negotiation */ + BIT(userhash); +#endif +}; +#endif + +typedef enum { + NTLMSTATE_NONE, + NTLMSTATE_TYPE1, + NTLMSTATE_TYPE2, + NTLMSTATE_TYPE3, + NTLMSTATE_LAST +} curlntlm; + +typedef enum { + GSS_AUTHNONE, + GSS_AUTHRECV, + GSS_AUTHSENT, + GSS_AUTHDONE, + GSS_AUTHSUCC +} curlnegotiate; + +/* Struct used for GSSAPI (Kerberos V5) authentication */ +#if defined(USE_KERBEROS5) +struct kerberos5data { +#if defined(USE_WINDOWS_SSPI) + CredHandle *credentials; + CtxtHandle *context; + TCHAR *spn; + SEC_WINNT_AUTH_IDENTITY identity; + SEC_WINNT_AUTH_IDENTITY *p_identity; + size_t token_max; + BYTE *output_token; +#else + gss_ctx_id_t context; + gss_name_t spn; +#endif +}; +#endif + +/* Struct used for SCRAM-SHA-1 authentication */ +#ifdef USE_GSASL +#include <gsasl.h> +struct gsasldata { + Gsasl *ctx; + Gsasl_session *client; +}; +#endif + +/* Struct used for NTLM challenge-response authentication */ +#if defined(USE_NTLM) +struct ntlmdata { +#ifdef USE_WINDOWS_SSPI +/* 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; + size_t token_max; + BYTE *output_token; + BYTE *input_token; + size_t input_token_len; + TCHAR *spn; +#else + unsigned int flags; + unsigned char nonce[8]; + unsigned int target_info_len; + void *target_info; /* TargetInfo received in the ntlm type-2 message */ + +#if defined(NTLM_WB_ENABLED) + /* used for communication with Samba's winbind daemon helper ntlm_auth */ + curl_socket_t ntlm_auth_hlpr_socket; + pid_t ntlm_auth_hlpr_pid; + char *challenge; /* The received base64 encoded ntlm type-2 message */ + char *response; /* The generated base64 ntlm type-1/type-3 message */ +#endif +#endif +}; +#endif + +/* Struct used for Negotiate (SPNEGO) authentication */ +#ifdef USE_SPNEGO +struct negotiatedata { +#ifdef HAVE_GSSAPI + OM_uint32 status; + gss_ctx_id_t context; + gss_name_t spn; + gss_buffer_desc output_token; +#else +#ifdef USE_WINDOWS_SSPI +#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS + CtxtHandle *sslContext; +#endif + DWORD status; + CredHandle *credentials; + CtxtHandle *context; + SEC_WINNT_AUTH_IDENTITY identity; + SEC_WINNT_AUTH_IDENTITY *p_identity; + TCHAR *spn; + size_t token_max; + BYTE *output_token; + size_t output_token_length; +#endif +#endif + BIT(noauthpersist); + BIT(havenoauthpersist); + BIT(havenegdata); + BIT(havemultiplerequests); +}; +#endif + +#ifdef CURL_DISABLE_PROXY +#define CONN_IS_PROXIED(x) 0 +#else +#define CONN_IS_PROXIED(x) x->bits.proxy +#endif + +/* + * Boolean values that concerns this connection. + */ +struct ConnectBits { +#ifndef CURL_DISABLE_PROXY + BIT(httpproxy); /* if set, this transfer is done through an HTTP proxy */ + BIT(socksproxy); /* if set, this transfer is done through a socks proxy */ + BIT(proxy_user_passwd); /* user+password for the proxy? */ + BIT(tunnel_proxy); /* if CONNECT is used to "tunnel" through the proxy. + This is implicit when SSL-protocols are used through + proxies, but can also be enabled explicitly by + apps */ + BIT(proxy_connect_closed); /* TRUE if a proxy disconnected the connection + in a CONNECT request with auth, so that + libcurl should reconnect and continue. */ + BIT(proxy); /* if set, this transfer is done through a proxy - any type */ +#endif + /* always modify bits.close with the connclose() and connkeep() macros! */ + BIT(close); /* if set, we close the connection after this request */ + BIT(reuse); /* if set, this is a reused connection */ + BIT(altused); /* this is an alt-svc "redirect" */ + BIT(conn_to_host); /* if set, this connection has a "connect to host" + that overrides the host in the URL */ + BIT(conn_to_port); /* if set, this connection has a "connect to port" + that overrides the port in the URL (remote port) */ + BIT(ipv6_ip); /* we communicate with a remote site specified with pure IPv6 + IP address */ + BIT(ipv6); /* we communicate with a site using an IPv6 address */ + BIT(do_more); /* this is set TRUE if the ->curl_do_more() function is + supposed to be called, after ->curl_do() */ + BIT(protoconnstart);/* the protocol layer has STARTED its operation after + the TCP layer connect */ + BIT(retry); /* this connection is about to get closed and then + re-attempted at another connection. */ + BIT(authneg); /* TRUE when the auth phase has started, which means + that we are creating a request with an auth header, + but it is not the final request in the auth + negotiation. */ +#ifndef CURL_DISABLE_FTP + BIT(ftp_use_epsv); /* As set with CURLOPT_FTP_USE_EPSV, but if we find out + EPSV doesn't work we disable it for the forthcoming + requests */ + BIT(ftp_use_eprt); /* As set with CURLOPT_FTP_USE_EPRT, but if we find out + EPRT doesn't work we disable it for the forthcoming + requests */ + BIT(ftp_use_data_ssl); /* Enabled SSL for the data connection */ + BIT(ftp_use_control_ssl); /* Enabled SSL for the control connection */ +#endif +#ifndef CURL_DISABLE_NETRC + BIT(netrc); /* name+password provided by netrc */ +#endif + BIT(bound); /* set true if bind() has already been done on this socket/ + connection */ + BIT(multiplex); /* connection is multiplexed */ + BIT(tcp_fastopen); /* use TCP Fast Open */ + BIT(tls_enable_alpn); /* TLS ALPN extension? */ +#ifndef CURL_DISABLE_DOH + BIT(doh); +#endif +#ifdef USE_UNIX_SOCKETS + BIT(abstract_unix_socket); +#endif + BIT(tls_upgraded); + BIT(sock_accepted); /* TRUE if the SECONDARYSOCKET was created with + accept() */ + BIT(parallel_connect); /* set TRUE when a parallel connect attempt has + started (happy eyeballs) */ +}; + +struct hostname { + char *rawalloc; /* allocated "raw" version of the name */ + char *encalloc; /* allocated IDN-encoded version of the name */ + char *name; /* name to use internally, might be encoded, might be raw */ + const char *dispname; /* name to display, as 'name' might be encoded */ +}; + +/* + * Flags on the keepon member of the Curl_transfer_keeper + */ + +#define KEEP_NONE 0 +#define KEEP_RECV (1<<0) /* there is or may be data to read */ +#define KEEP_SEND (1<<1) /* there is or may be data to write */ +#define KEEP_RECV_HOLD (1<<2) /* when set, no reading should be done but there + might still be data to read */ +#define KEEP_SEND_HOLD (1<<3) /* when set, no writing should be done but there + might still be data to write */ +#define KEEP_RECV_PAUSE (1<<4) /* reading is paused */ +#define KEEP_SEND_PAUSE (1<<5) /* writing is paused */ + +#define KEEP_RECVBITS (KEEP_RECV | KEEP_RECV_HOLD | KEEP_RECV_PAUSE) +#define KEEP_SENDBITS (KEEP_SEND | KEEP_SEND_HOLD | KEEP_SEND_PAUSE) + +/* transfer wants to send is not PAUSE or HOLD */ +#define CURL_WANT_SEND(data) \ + (((data)->req.keepon & KEEP_SENDBITS) == KEEP_SEND) +/* transfer receive is not on PAUSE or HOLD */ +#define CURL_WANT_RECV(data) \ + (!((data)->req.keepon & (KEEP_RECV_PAUSE|KEEP_RECV_HOLD))) + +#if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) +#define USE_CURL_ASYNC +struct Curl_async { + char *hostname; + struct Curl_dns_entry *dns; + struct thread_data *tdata; + void *resolver; /* resolver state, if it is used in the URL state - + ares_channel e.g. */ + int port; + int status; /* if done is TRUE, this is the status from the callback */ + BIT(done); /* set TRUE when the lookup is complete */ +}; + +#endif + +#define FIRSTSOCKET 0 +#define SECONDARYSOCKET 1 + +/* Polling requested by an easy handle. + * `action` is CURL_POLL_IN, CURL_POLL_OUT or CURL_POLL_INOUT. + */ +struct easy_pollset { + curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE]; + unsigned int num; + unsigned char actions[MAX_SOCKSPEREASYHANDLE]; +}; + +enum expect100 { + EXP100_SEND_DATA, /* enough waiting, just send the body now */ + EXP100_AWAITING_CONTINUE, /* waiting for the 100 Continue header */ + EXP100_SENDING_REQUEST, /* still sending the request but will wait for + the 100 header once done with the request */ + EXP100_FAILED /* used on 417 Expectation Failed */ +}; + +enum upgrade101 { + UPGR101_INIT, /* default state */ + UPGR101_WS, /* upgrade to WebSockets requested */ + UPGR101_H2, /* upgrade to HTTP/2 requested */ + UPGR101_RECEIVED, /* 101 response received */ + UPGR101_WORKING /* talking upgraded protocol */ +}; + +enum doh_slots { + /* Explicit values for first two symbols so as to match hard-coded + * constants in existing code + */ + DOH_PROBE_SLOT_IPADDR_V4 = 0, /* make 'V4' stand out for readability */ + DOH_PROBE_SLOT_IPADDR_V6 = 1, /* 'V6' likewise */ + + /* Space here for (possibly build-specific) additional slot definitions */ + + /* for example */ + /* #ifdef WANT_DOH_FOOBAR_TXT */ + /* DOH_PROBE_SLOT_FOOBAR_TXT, */ + /* #endif */ + + /* AFTER all slot definitions, establish how many we have */ + DOH_PROBE_SLOTS +}; + +/* + * Request specific data in the easy handle (Curl_easy). Previously, + * these members were on the connectdata struct but since a conn struct may + * now be shared between different Curl_easys, we store connection-specific + * data here. This struct only keeps stuff that's interesting for *this* + * request, as it will be cleared between multiple ones + */ +struct SingleRequest { + curl_off_t size; /* -1 if unknown at this point */ + curl_off_t maxdownload; /* in bytes, the maximum amount of data to fetch, + -1 means unlimited */ + curl_off_t bytecount; /* total number of bytes read */ + curl_off_t writebytecount; /* number of bytes written */ + + curl_off_t pendingheader; /* this many bytes left to send is actually + header and not body */ + struct curltime start; /* transfer started at this time */ + unsigned int headerbytecount; /* received server headers (not CONNECT + headers) */ + unsigned int allheadercount; /* all received headers (server + CONNECT) */ + unsigned int deductheadercount; /* this amount of bytes doesn't count when + we check if anything has been transferred + at the end of a connection. We use this + counter to make only a 100 reply (without + a following second response code) result + in a CURLE_GOT_NOTHING error code */ + int headerline; /* counts header lines to better track the + first one */ + curl_off_t offset; /* possible resume offset read from the + Content-Range: header */ + int httpcode; /* error code from the 'HTTP/1.? XXX' or + 'RTSP/1.? XXX' line */ + int keepon; + struct curltime start100; /* time stamp to wait for the 100 code from */ + enum expect100 exp100; /* expect 100 continue state */ + enum upgrade101 upgr101; /* 101 upgrade state */ + + /* Content unencoding stack. See sec 3.5, RFC2616. */ + struct Curl_cwriter *writer_stack; + time_t timeofdoc; + long bodywrites; + char *location; /* This points to an allocated version of the Location: + header data */ + char *newurl; /* Set to the new URL to use when a redirect or a retry is + wanted */ + + /* 'upload_present' is used to keep a byte counter of how much data there is + still left in the buffer, aimed for upload. */ + ssize_t upload_present; + + /* 'upload_fromhere' is used as a read-pointer when we uploaded parts of a + buffer, so the next read should read from where this pointer points to, + and the 'upload_present' contains the number of bytes available at this + position */ + char *upload_fromhere; + + /* Allocated protocol-specific data. Each protocol handler makes sure this + points to data it needs. */ + union { + struct FILEPROTO *file; + struct FTP *ftp; + struct HTTP *http; + struct IMAP *imap; + struct ldapreqinfo *ldap; + struct MQTT *mqtt; + struct POP3 *pop3; + struct RTSP *rtsp; + struct smb_request *smb; + struct SMTP *smtp; + struct SSHPROTO *ssh; + struct TELNET *telnet; + } p; +#ifndef CURL_DISABLE_DOH + struct dohdata *doh; /* DoH specific data for this request */ +#endif +#if defined(_WIN32) && defined(USE_WINSOCK) + struct curltime last_sndbuf_update; /* last time readwrite_upload called + win_update_buffer_size */ +#endif + char fread_eof[2]; /* the body read callback (index 0) returned EOF or + the trailer read callback (index 1) returned EOF */ +#ifndef CURL_DISABLE_COOKIES + unsigned char setcookies; +#endif + unsigned char writer_stack_depth; /* Unencoding stack depth. */ + BIT(header); /* incoming data has HTTP header */ + BIT(badheader); /* header parsing found sth not a header */ + BIT(content_range); /* set TRUE if Content-Range: was found */ + BIT(download_done); /* set to TRUE when download is complete */ + BIT(upload_done); /* set to TRUE when doing chunked transfer-encoding + upload and we're uploading the last chunk */ + BIT(ignorebody); /* we read a response-body but we ignore it! */ + BIT(http_bodyless); /* HTTP response status code is between 100 and 199, + 204 or 304 */ + BIT(chunk); /* if set, this is a chunked transfer-encoding */ + BIT(ignore_cl); /* ignore content-length */ + BIT(upload_chunky); /* set TRUE if we are doing chunked transfer-encoding + on upload */ + BIT(getheader); /* TRUE if header parsing is wanted */ + BIT(forbidchunk); /* used only to explicitly forbid chunk-upload for + specific upload buffers. See readmoredata() in http.c + for details. */ + BIT(no_body); /* the response has no body */ +}; + +/* + * Specific protocol handler. + */ + +struct Curl_handler { + const char *scheme; /* URL scheme name. */ + + /* Complement to setup_connection_internals(). This is done before the + transfer "owns" the connection. */ + CURLcode (*setup_connection)(struct Curl_easy *data, + struct connectdata *conn); + + /* These two functions MUST be set to be protocol dependent */ + CURLcode (*do_it)(struct Curl_easy *data, bool *done); + CURLcode (*done)(struct Curl_easy *, CURLcode, bool); + + /* If the curl_do() function is better made in two halves, this + * curl_do_more() function will be called afterwards, if set. For example + * for doing the FTP stuff after the PASV/PORT command. + */ + CURLcode (*do_more)(struct Curl_easy *, int *); + + /* This function *MAY* be set to a protocol-dependent function that is run + * after the connect() and everything is done, as a step in the connection. + * The 'done' pointer points to a bool that should be set to TRUE if the + * function completes before return. If it doesn't complete, the caller + * should call the ->connecting() function until it is. + */ + CURLcode (*connect_it)(struct Curl_easy *data, bool *done); + + /* See above. */ + CURLcode (*connecting)(struct Curl_easy *data, bool *done); + CURLcode (*doing)(struct Curl_easy *data, bool *done); + + /* Called from the multi interface during the PROTOCONNECT phase, and it + should then return a proper fd set */ + int (*proto_getsock)(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); + + /* Called from the multi interface during the DOING phase, and it should + then return a proper fd set */ + int (*doing_getsock)(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); + + /* Called from the multi interface during the DO_MORE phase, and it should + then return a proper fd set */ + int (*domore_getsock)(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); + + /* Called from the multi interface during the DO_DONE, PERFORM and + WAITPERFORM phases, and it should then return a proper fd set. Not setting + this will make libcurl use the generic default one. */ + int (*perform_getsock)(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); + + /* This function *MAY* be set to a protocol-dependent function that is run + * by the curl_disconnect(), as a step in the disconnection. If the handler + * is called because the connection has been considered dead, + * dead_connection is set to TRUE. The connection is (again) associated with + * the transfer here. + */ + CURLcode (*disconnect)(struct Curl_easy *, struct connectdata *, + bool dead_connection); + + /* If used, this function gets called from transfer.c:readwrite_data() to + allow the protocol to do extra reads/writes */ + CURLcode (*readwrite)(struct Curl_easy *data, struct connectdata *conn, + const char *buf, size_t blen, + size_t *pconsumed, bool *readmore); + + /* This function can perform various checks on the connection. See + CONNCHECK_* for more information about the checks that can be performed, + and CONNRESULT_* for the results that can be returned. */ + unsigned int (*connection_check)(struct Curl_easy *data, + struct connectdata *conn, + unsigned int checks_to_perform); + + /* attach() attaches this transfer to this connection */ + void (*attach)(struct Curl_easy *data, struct connectdata *conn); + + int defport; /* Default port. */ + curl_prot_t protocol; /* See CURLPROTO_* - this needs to be the single + specific protocol bit */ + curl_prot_t family; /* single bit for protocol family; basically the + non-TLS name of the protocol this is */ + unsigned int flags; /* Extra particular characteristics, see PROTOPT_* */ + +}; + +#define PROTOPT_NONE 0 /* nothing extra */ +#define PROTOPT_SSL (1<<0) /* uses SSL */ +#define PROTOPT_DUAL (1<<1) /* this protocol uses two connections */ +#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. +*/ +#define PROTOPT_DIRLOCK (1<<3) +#define PROTOPT_NONETWORK (1<<4) /* protocol doesn't use the network! */ +#define PROTOPT_NEEDSPWD (1<<5) /* needs a password, and if none is set it + gets a default */ +#define PROTOPT_NOURLQUERY (1<<6) /* protocol can't handle + url query strings (?foo=bar) ! */ +#define PROTOPT_CREDSPERREQUEST (1<<7) /* requires login credentials per + request instead of per connection */ +#define PROTOPT_ALPN (1<<8) /* set ALPN for this */ +/* (1<<9) was PROTOPT_STREAM, now free */ +#define PROTOPT_URLOPTIONS (1<<10) /* allow options part in the userinfo field + of the URL */ +#define PROTOPT_PROXY_AS_HTTP (1<<11) /* allow this non-HTTP scheme over a + HTTP proxy as HTTP proxies may know + this protocol and act as a gateway */ +#define PROTOPT_WILDCARD (1<<12) /* protocol supports wildcard matching */ +#define PROTOPT_USERPWDCTRL (1<<13) /* Allow "control bytes" (< 32 ascii) in + user name and password */ +#define PROTOPT_NOTCPPROXY (1<<14) /* this protocol can't proxy over TCP */ + +#define CONNCHECK_NONE 0 /* No checks */ +#define CONNCHECK_ISDEAD (1<<0) /* Check if the connection is dead. */ +#define CONNCHECK_KEEPALIVE (1<<1) /* Perform any keepalive function. */ + +#define CONNRESULT_NONE 0 /* No extra information. */ +#define CONNRESULT_DEAD (1<<0) /* The connection is dead. */ + +struct proxy_info { + struct hostname host; + int port; + unsigned char proxytype; /* curl_proxytype: what kind of proxy that is in + use */ + char *user; /* proxy user name string, allocated */ + char *passwd; /* proxy password string, allocated */ +}; + +struct ldapconninfo; + +#define TRNSPRT_TCP 3 +#define TRNSPRT_UDP 4 +#define TRNSPRT_QUIC 5 +#define TRNSPRT_UNIX 6 + +/* + * The connectdata struct contains all fields and variables that should be + * unique for an entire connection. + */ +struct connectdata { + struct Curl_llist_element bundle_node; /* conncache */ + + /* chunk is for HTTP chunked encoding, but is in the general connectdata + struct only because we can do just about any protocol through an HTTP + proxy and an HTTP proxy may in fact respond using chunked encoding */ + struct Curl_chunker chunk; + + curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ + void *closesocket_client; + + /* This is used by the connection cache 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)->easyq.size) + + /**** 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 */ + + /* 'dns_entry' is the particular host we use. This points to an entry in the + DNS cache and it will not get pruned while locked. It gets unlocked in + multi_done(). This entry will be NULL if the connection is reused as then + there is no name resolve done. */ + struct Curl_dns_entry *dns_entry; +#ifdef USE_CURL_ASYNC + struct Curl_async resolve_async; /* asynchronous name resolver data */ +#endif + + /* 'remote_addr' is the particular IP we connected to. it is owned, set + * and NULLed by the connected socket filter (if there is one). */ + const struct Curl_sockaddr_ex *remote_addr; + + struct hostname host; + char *hostname_resolve; /* host name to resolve to address, allocated */ + char *secondaryhostname; /* secondary socket host name (ftp) */ + struct hostname conn_to_host; /* the host to connect to. valid only if + bits.conn_to_host is set */ +#ifndef CURL_DISABLE_PROXY + struct proxy_info socks_proxy; + struct proxy_info http_proxy; +#endif + /* 'primary_ip' and 'primary_port' get filled with peer's numerical + ip address and port number whenever an outgoing connection is + *attempted* from the primary socket to a remote address. When more + than one address is tried for a connection these will hold data + for the last attempt. When the connection is actually established + these are updated with data which comes directly from the socket. */ + + char primary_ip[MAX_IPADR_LEN]; + char *user; /* user name string, allocated */ + char *passwd; /* password string, allocated */ + char *options; /* options string, allocated */ + char *sasl_authzid; /* authorization identity string, allocated */ + char *oauth_bearer; /* OAUTH2 bearer, allocated */ + struct curltime now; /* "current" time */ + struct curltime created; /* creation time */ + struct curltime lastused; /* when returned to the connection cache */ + curl_socket_t sock[2]; /* two sockets, the second is used for the data + transfer when doing FTP */ + Curl_recv *recv[2]; + Curl_send *send[2]; + struct Curl_cfilter *cfilter[2]; /* connection filters */ + + 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_handler *handler; /* Connection's protocol handler */ + const struct Curl_handler *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; + + /**** curl_get() phase fields */ + + curl_socket_t sockfd; /* socket to read from or CURL_SOCKET_BAD */ + curl_socket_t writesockfd; /* socket to write to, it may very + well be the same we read from. + CURL_SOCKET_BAD disables */ + +#ifdef HAVE_GSSAPI + BIT(sec_complete); /* if Kerberos is enabled for this connection */ + unsigned char command_prot; /* enum protection_level */ + unsigned char data_prot; /* enum protection_level */ + unsigned char request_data_prot; /* enum protection_level */ + size_t buffer_size; + struct krb5buffer in_buffer; + void *app_data; + const struct Curl_sec_client_mech *mech; + struct sockaddr_in local_addr; +#endif + +#if defined(USE_KERBEROS5) /* Consider moving some of the above GSS-API */ + struct kerberos5data krb5; /* variables into the structure definition, */ +#endif /* however, some of them are ftp specific. */ + + struct Curl_llist easyq; /* List of easy handles using this connection */ + curl_seek_callback seek_func; /* function that seeks the input */ + void *seek_client; /* pointer to pass to the seek() above */ + + /*************** Request - specific items ************/ +#if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) + CtxtHandle *sslContext; +#endif + +#ifdef USE_GSASL + struct gsasldata gsasl; +#endif + +#if defined(USE_NTLM) + curlntlm http_ntlm_state; + curlntlm proxy_ntlm_state; + + struct ntlmdata ntlm; /* NTLM differs from other authentication schemes + because it authenticates connections, not + single requests! */ + struct ntlmdata proxyntlm; /* NTLM data for proxy */ +#endif + +#ifdef USE_SPNEGO + curlnegotiate http_negotiate_state; + curlnegotiate proxy_negotiate_state; + + struct negotiatedata negotiate; /* state data for host Negotiate auth */ + struct negotiatedata proxyneg; /* state data for proxy Negotiate auth */ +#endif + +#ifndef CURL_DISABLE_HTTP + /* for chunked-encoded trailer */ + struct dynbuf trailer; +#endif + + union { +#ifndef CURL_DISABLE_FTP + struct ftp_conn ftpc; +#endif +#ifdef USE_SSH + struct ssh_conn sshc; +#endif +#ifndef CURL_DISABLE_TFTP + struct tftp_state_data *tftpc; +#endif +#ifndef CURL_DISABLE_IMAP + struct imap_conn imapc; +#endif +#ifndef CURL_DISABLE_POP3 + struct pop3_conn pop3c; +#endif +#ifndef CURL_DISABLE_SMTP + struct smtp_conn smtpc; +#endif +#ifndef CURL_DISABLE_RTSP + struct rtsp_conn rtspc; +#endif +#ifndef CURL_DISABLE_SMB + struct smb_conn smbc; +#endif +#ifdef USE_LIBRTMP + void *rtmp; +#endif +#ifdef USE_OPENLDAP + struct ldapconninfo *ldapc; +#endif +#ifndef CURL_DISABLE_MQTT + struct mqtt_conn mqtt; +#endif +#ifdef USE_WEBSOCKETS + struct websocket *ws; +#endif + unsigned int unused:1; /* avoids empty union */ + } proto; + + struct connectbundle *bundle; /* The bundle we are member of */ +#ifdef USE_UNIX_SOCKETS + char *unix_domain_socket; +#endif +#ifdef USE_HYPER + /* if set, an alternative data transfer function */ + Curl_datastream datastream; +#endif + /* When this connection is created, store the conditions for the local end + bind. This is stored before the actual bind and before any connection is + made and will serve the purpose of being used for comparison reasons so + that subsequent bound-requested connections aren't accidentally reusing + wrong connections. */ + char *localdev; + unsigned short localportrange; + int waitfor; /* current READ/WRITE bits to wait for */ +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + int socks5_gssapi_enctype; +#endif + /* The field below gets set in connect.c:connecthost() */ + int port; /* which port to use locally - to connect to */ + int remote_port; /* the remote port, not the proxy port! */ + int conn_to_port; /* the remote port to connect to. valid only if + bits.conn_to_port is set */ +#ifdef ENABLE_IPV6 + unsigned int scope_id; /* Scope id for IPv6 */ +#endif + unsigned short localport; + unsigned short secondary_port; /* secondary socket remote port to connect to + (ftp) */ + unsigned char cselect_bits; /* bitmask of socket events */ + unsigned char alpn; /* APLN TLS negotiated protocol, a CURL_HTTP_VERSION* + value */ +#ifndef CURL_DISABLE_PROXY + unsigned char proxy_alpn; /* APLN of proxy tunnel, CURL_HTTP_VERSION* */ +#endif + unsigned char transport; /* one of the TRNSPRT_* defines */ + unsigned char ip_version; /* copied from the Curl_easy at creation time */ + unsigned char httpversion; /* the HTTP version*10 reported by the server */ + unsigned char connect_only; + unsigned char gssapi_delegation; /* inherited from set.gssapi_delegation */ +}; + +#ifndef CURL_DISABLE_PROXY +#define CURL_CONN_HOST_DISPNAME(c) \ + ((c)->bits.socksproxy ? (c)->socks_proxy.host.dispname : \ + (c)->bits.httpproxy ? (c)->http_proxy.host.dispname : \ + (c)->bits.conn_to_host ? (c)->conn_to_host.dispname : \ + (c)->host.dispname) +#else +#define CURL_CONN_HOST_DISPNAME(c) \ + (c)->bits.conn_to_host ? (c)->conn_to_host.dispname : \ + (c)->host.dispname +#endif + +/* The end of connectdata. */ + +/* + * Struct to keep statistical and informational data. + * 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) */ + unsigned long proxyauthavail; /* what proxy auth types were announced */ + unsigned long httpauthavail; /* what host auth types were announced */ + long numconnects; /* how many new connection did libcurl created */ + char *contenttype; /* the content type of the object */ + char *wouldredirect; /* URL this would've been redirected to if asked to */ + curl_off_t retry_after; /* info from Retry-After: header */ + unsigned int header_size; /* size of read header(s) in bytes */ + + /* PureInfo members 'conn_primary_ip', 'conn_primary_port', 'conn_local_ip' + and, 'conn_local_port' are 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, and also + allow curl_easy_reset() to clear this information from the session handle + without disturbing information which is still alive, and that might be + reused, in the connection cache. */ + + char conn_primary_ip[MAX_IPADR_LEN]; + int conn_primary_port; /* this is the destination port to the connection, + which might have been a proxy */ + int conn_remote_port; /* this is the "remote port", which is the port + number of the used URL, independent of proxy or + not */ + char conn_local_ip[MAX_IPADR_LEN]; + int conn_local_port; + const char *conn_scheme; + unsigned int conn_protocol; + struct curl_certinfo certs; /* info about the certs. Asked for with + CURLOPT_CERTINFO / CURLINFO_CERTINFO */ + CURLproxycode pxcode; + BIT(timecond); /* set to TRUE if the time condition didn't match, which + thus made the document NOT get fetched */ +}; + + +struct Progress { + time_t lastshow; /* time() of the last displayed progress meter or NULL to + force redraw at next call */ + curl_off_t size_dl; /* total expected size */ + curl_off_t size_ul; /* total expected size */ + curl_off_t downloaded; /* transferred so far */ + curl_off_t uploaded; /* transferred so far */ + + curl_off_t current_speed; /* uses the currently fastest transfer */ + + int width; /* screen width at download start */ + int flags; /* see progress.h */ + + timediff_t timespent; + + curl_off_t dlspeed; + curl_off_t ulspeed; + + timediff_t t_nslookup; + timediff_t t_connect; + timediff_t t_appconnect; + timediff_t t_pretransfer; + timediff_t t_starttransfer; + timediff_t t_redirect; + + struct curltime start; + struct curltime t_startsingle; + struct curltime t_startop; + struct curltime t_acceptdata; + + + /* upload speed limit */ + struct curltime ul_limit_start; + curl_off_t ul_limit_size; + /* download speed limit */ + struct curltime dl_limit_start; + curl_off_t dl_limit_size; + +#define CURR_TIME (5 + 1) /* 6 entries for 5 seconds */ + + curl_off_t speeder[ CURR_TIME ]; + struct curltime speeder_time[ CURR_TIME ]; + int speeder_c; + BIT(callback); /* set when progress callback is used */ + BIT(is_t_startransfer_set); +}; + +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 { + unsigned long want; /* Bitmask set to the authentication methods wanted by + app (with CURLOPT_HTTPAUTH or CURLOPT_PROXYAUTH). */ + unsigned long picked; + unsigned long avail; /* Bitmask for what the server reports to support for + this resource */ + BIT(done); /* TRUE when the auth phase is done and ready to do the + actual request */ + BIT(multipass); /* TRUE if this is not yet authenticated but within the + auth multipass negotiation */ + BIT(iestyle); /* TRUE if digest should be done IE-style or FALSE if it + should be RFC compliant */ +}; + +#ifdef USE_NGHTTP2 +struct Curl_data_prio_node { + struct Curl_data_prio_node *next; + struct Curl_easy *data; +}; +#endif + +/** + * Priority information for an easy handle in relation to others + * on the same connection. + * TODO: we need to adapt it to the new priority scheme as defined in RFC 9218 + */ +struct Curl_data_priority { +#ifdef USE_NGHTTP2 + /* tree like dependencies only implemented in nghttp2 */ + struct Curl_easy *parent; + struct Curl_data_prio_node *children; +#endif + int weight; +#ifdef USE_NGHTTP2 + BIT(exclusive); +#endif +}; + +/* + * This struct is for holding data that was attempted to get sent to the user's + * callback but is held due to pausing. One instance per type (BOTH, HEADER, + * BODY). + */ +struct tempbuf { + struct dynbuf b; + int type; /* type of the 'tempwrite' buffer as a bitmask that is used with + Curl_client_write() */ + BIT(paused_body); /* if PAUSE happened before/during BODY write */ +}; + +/* 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_RUN_NOW, + EXPIRE_SPEEDCHECK, + EXPIRE_TIMEOUT, + EXPIRE_TOOFAST, + EXPIRE_QUIC, + EXPIRE_FTP_ACCEPT, + EXPIRE_ALPN_EYEBALLS, + EXPIRE_LAST /* not an actual timer, used as a marker only */ +} expire_id; + + +typedef enum { + TRAILERS_NONE, + TRAILERS_INITIALIZED, + TRAILERS_SENDING, + TRAILERS_DONE +} trailers_state; + + +/* + * One instance for each timeout an easy handle can set. + */ +struct time_node { + struct Curl_llist_element list; + struct curltime time; + expire_id eid; +}; + +/* 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 { + /* Points to the connection cache */ + struct conncache *conn_cache; + /* 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 */ + struct curl_slist *hstslist; /* list of HSTS files set by + curl_easy_setopt(HSTS) calls */ + char *buffer; /* download buffer */ + char *ulbuf; /* allocated upload buffer or NULL */ + curl_off_t current_speed; /* the ProgressShow() function sets this, + bytes / second */ + + /* host name, port number and protocol of the first (not followed) request. + if set, this should be the host name that we will sent authorization to, + no else. Used to make Location: following not keep sending user+password. + This is strdup()ed data. */ + char *first_host; + int first_remote_port; + curl_prot_t first_remote_protocol; + + int retrycount; /* number of retries on a new connection */ + struct Curl_ssl_session *session; /* array of 'max_ssl_sessions' size */ + long sessionage; /* number of the most recent session */ + struct tempbuf tempwrite[3]; /* BOTH, HEADER, BODY */ + unsigned int tempcount; /* number of entries in use in tempwrite, 0 - 3 */ + int os_errno; /* filled in with errno whenever an error occurs */ + char *scratch; /* huge buffer[set.buffer_size*2] for upload CRLF replacing */ + long followlocation; /* redirect counter */ + int requests; /* request counter: redirects + authentication retakes */ +#ifdef HAVE_SIGNAL + /* storage for the previous bag^H^H^HSIGPIPE signal handler :-) */ + void (*prev_signal)(int sig); +#endif +#ifndef CURL_DISABLE_DIGEST_AUTH + struct digestdata digest; /* state data for host Digest auth */ + struct digestdata proxydigest; /* state data for proxy Digest auth */ +#endif + struct auth authhost; /* auth details for host */ + struct auth authproxy; /* auth details for proxy */ + +#if defined(USE_OPENSSL) + /* void instead of ENGINE to avoid bleeding OpenSSL into this header */ + void *engine; +#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 */ + + /* a place to store the most recently set (S)FTP entrypath */ + char *most_recent_ftp_entrypath; +#if !defined(_WIN32) && !defined(MSDOS) && !defined(__EMX__) +/* do FTP line-end conversions on most platforms */ +#define CURL_DO_LINEEND_CONV + /* for FTP downloads: track CRLF sequences that span blocks */ + BIT(prev_block_had_trailing_cr); + /* for FTP downloads: how many CRLFs did we converted to LFs? */ + curl_off_t crlf_conversions; +#endif + 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 */ + long rtsp_next_client_CSeq; /* the session's next client CSeq */ + long rtsp_next_server_CSeq; /* the session's next server CSeq */ + long rtsp_CSeq_recv; /* most recent CSeq received */ + + unsigned char 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 */ + struct urlpieces up; + char *url; /* work URL, copied from UserDefined */ + char *referer; /* referer string */ + struct curl_slist *resolve; /* set to point to the set.resolve list when + this should be dealt with in pretransfer */ +#ifndef CURL_DISABLE_HTTP + curl_mimepart *mimepost; + curl_mimepart *formp; /* storage for old API form-posting, allocated on + demand */ + 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 */ + trailers_state trailers_state; /* whether we are sending trailers + and what stage are we at */ +#endif +#ifndef CURL_DISABLE_COOKIES + struct curl_slist *cookielist; /* list of cookie files set by + curl_easy_setopt(COOKIEFILE) calls */ +#endif +#ifdef USE_HYPER + bool hconnect; /* set if a CONNECT request */ + CURLcode hresult; /* used to pass return codes back from hyper callbacks */ +#endif + + /* Dynamically allocated strings, MUST be freed before this struct is + killed. */ + struct dynamically_allocated_data { + char *proxyuserpwd; + char *uagent; + char *accept_encoding; + char *userpwd; + char *rangeline; + char *ref; + char *host; + char *cookiehost; + char *rtsp_transport; + char *te; /* TE: request header */ + + /* transfer credentials */ + char *user; + char *passwd; + char *proxyuser; + char *proxypasswd; + } aptr; + + unsigned char httpwant; /* when non-zero, a specific HTTP version requested + to be used in the library's request(s) */ + unsigned char httpversion; /* the lowest HTTP version*10 reported by any + server involved in this request */ + unsigned char httpreq; /* Curl_HttpReq; what kind of HTTP request (if any) + is this */ + unsigned char dselect_bits; /* != 0 -> bitmask of socket events for this + transfer overriding anything the socket may + report */ +#ifdef CURLDEBUG + BIT(conncache_lock); +#endif + /* when curl_easy_perform() is called, the multi handle is "owned" by + the easy handle so curl_easy_cleanup() on such an easy handle will + also close the multi handle! */ + BIT(multi_owned_by_easy); + + BIT(this_is_a_follow); /* this is a followed Location: request */ + BIT(refused_stream); /* this was refused, try again */ + BIT(errorbuf); /* Set to TRUE if the error buffer is already filled in. + This must be set to FALSE every time _easy_perform() is + called. */ + BIT(allow_port); /* Is set.use_port allowed to take effect or not. This + is always set TRUE when curl_easy_perform() is called. */ + BIT(authproblem); /* TRUE if there's some problem authenticating */ + /* set after initial USER failure, to prevent an authentication loop */ + BIT(wildcardmatch); /* enable wildcard matching */ + BIT(expect100header); /* TRUE if we added Expect: 100-continue */ + BIT(disableexpect); /* TRUE if Expect: is disabled due to a previous + 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(previouslypending); /* this transfer WAS in the multi->pending queue */ +#ifndef CURL_DISABLE_COOKIES + BIT(cookie_engine); +#endif + BIT(prefer_ascii); /* ASCII rather than binary */ +#ifdef CURL_LIST_ONLY_PROTOCOL + BIT(list_only); /* list directory contents */ +#endif + BIT(url_alloc); /* URL string is malloc()'ed */ + BIT(referer_alloc); /* referer string is malloc()ed */ + BIT(wildcard_resolve); /* Set to true if any resolve change is a wildcard */ + BIT(rewindbeforesend);/* TRUE when the sending couldn't be stopped even + though it will be discarded. We must call the data + rewind callback before trying to send again. */ + BIT(upload); /* upload request */ + BIT(internal); /* internal: true if this easy handle was created for + internal use and the user does not have ownership of the + handle. */ +}; + +/* + * This 'UserDefined' struct must only contain data that is set once to go + * for many (perhaps) independent connections. Values that are generated or + * calculated internally for the "session handle" MUST be defined within the + * 'struct UrlState' instead. The only exceptions MUST note the changes in + * the 'DynamicStatic' struct. + * Character pointer fields point to dynamic storage, unless otherwise stated. + */ + +struct Curl_multi; /* declared in multihandle.c */ + +/* + * This enumeration MUST not use conditional directives (#ifdefs), new + * null terminated strings MUST be added to the enumeration immediately + * before STRING_LASTZEROTERMINATED, binary fields immediately before + * STRING_LAST. When doing so, ensure that the packages/OS400/chkstring.c + * test is updated and applicable changes for EBCDIC to ASCII conversion + * are catered for in curl_easy_setopt_ccsid() + */ +enum dupstring { + STRING_CERT, /* client certificate file name */ + STRING_CERT_PROXY, /* client certificate file name */ + STRING_CERT_TYPE, /* format for certificate (default: PEM)*/ + STRING_CERT_TYPE_PROXY, /* format for certificate (default: PEM)*/ + STRING_COOKIE, /* HTTP cookie string to send */ + STRING_COOKIEJAR, /* dump all cookies to this file */ + STRING_CUSTOMREQUEST, /* HTTP/FTP/RTSP request/method to use */ + STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL doesn't specify */ + STRING_DEVICE, /* local network interface/address to use */ + STRING_ENCODING, /* Accept-Encoding string */ + STRING_FTP_ACCOUNT, /* ftp account data */ + STRING_FTP_ALTERNATIVE_TO_USER, /* command to send if USER/PASS fails */ + STRING_FTPPORT, /* port to send with the FTP PORT command */ + STRING_KEY, /* private key file name */ + STRING_KEY_PROXY, /* private key file name */ + STRING_KEY_PASSWD, /* plain text private key password */ + STRING_KEY_PASSWD_PROXY, /* plain text private key password */ + STRING_KEY_TYPE, /* format for private key (default: PEM) */ + STRING_KEY_TYPE_PROXY, /* format for private key (default: PEM) */ + STRING_KRB_LEVEL, /* krb security level */ + STRING_NETRC_FILE, /* if not NULL, use this instead of trying to find + $HOME/.netrc */ + STRING_PROXY, /* proxy to use */ + STRING_PRE_PROXY, /* pre socks proxy to use */ + STRING_SET_RANGE, /* range, if used */ + STRING_SET_REFERER, /* custom string for the HTTP referer field */ + STRING_SET_URL, /* what original URL to work on */ + STRING_SSL_CAPATH, /* CA directory name (doesn't work on windows) */ + STRING_SSL_CAPATH_PROXY, /* CA directory name (doesn't work on windows) */ + STRING_SSL_CAFILE, /* certificate file to verify peer against */ + STRING_SSL_CAFILE_PROXY, /* certificate file to verify peer against */ + STRING_SSL_PINNEDPUBLICKEY, /* public key file to verify peer against */ + STRING_SSL_PINNEDPUBLICKEY_PROXY, /* public key file to verify proxy */ + STRING_SSL_CIPHER_LIST, /* list of ciphers to use */ + STRING_SSL_CIPHER_LIST_PROXY, /* list of ciphers to use */ + STRING_SSL_CIPHER13_LIST, /* list of TLS 1.3 ciphers to use */ + STRING_SSL_CIPHER13_LIST_PROXY, /* list of TLS 1.3 ciphers to use */ + STRING_USERAGENT, /* User-Agent string */ + STRING_SSL_CRLFILE, /* crl file to check certificate */ + STRING_SSL_CRLFILE_PROXY, /* crl file to check certificate */ + STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */ + STRING_SSL_ISSUERCERT_PROXY, /* issuer cert file to check certificate */ + STRING_SSL_ENGINE, /* name of ssl engine */ + STRING_USERNAME, /* <username>, if used */ + STRING_PASSWORD, /* <password>, if used */ + STRING_OPTIONS, /* <options>, if used */ + STRING_PROXYUSERNAME, /* Proxy <username>, if used */ + STRING_PROXYPASSWORD, /* Proxy <password>, if used */ + STRING_NOPROXY, /* List of hosts which should not use the proxy, if + used */ + STRING_RTSP_SESSION_ID, /* Session ID to use */ + STRING_RTSP_STREAM_URI, /* Stream URI for this request */ + STRING_RTSP_TRANSPORT, /* Transport for this session */ + STRING_SSH_PRIVATE_KEY, /* path to the private key file for auth */ + STRING_SSH_PUBLIC_KEY, /* path to the public key file for auth */ + STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */ + STRING_SSH_HOST_PUBLIC_KEY_SHA256, /* sha256 of host public key in base64 */ + STRING_SSH_KNOWNHOSTS, /* file name of knownhosts file */ + STRING_PROXY_SERVICE_NAME, /* Proxy service name */ + STRING_SERVICE_NAME, /* Service name */ + STRING_MAIL_FROM, + STRING_MAIL_AUTH, + STRING_TLSAUTH_USERNAME, /* TLS auth <username> */ + STRING_TLSAUTH_USERNAME_PROXY, /* TLS auth <username> */ + STRING_TLSAUTH_PASSWORD, /* TLS auth <password> */ + STRING_TLSAUTH_PASSWORD_PROXY, /* TLS auth <password> */ + STRING_BEARER, /* <bearer>, if used */ + STRING_UNIX_SOCKET_PATH, /* path to Unix socket, if used */ + STRING_TARGET, /* CURLOPT_REQUEST_TARGET */ + STRING_DOH, /* CURLOPT_DOH_URL */ + STRING_ALTSVC, /* CURLOPT_ALTSVC */ + STRING_HSTS, /* CURLOPT_HSTS */ + STRING_SASL_AUTHZID, /* CURLOPT_SASL_AUTHZID */ + STRING_DNS_SERVERS, + STRING_DNS_INTERFACE, + STRING_DNS_LOCAL_IP4, + STRING_DNS_LOCAL_IP6, + STRING_SSL_EC_CURVES, + STRING_AWS_SIGV4, /* Parameters for V4 signature */ + STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */ + + /* -- 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, just an end-of-list marker */ +}; + +enum dupblob { + BLOB_CERT, + BLOB_CERT_PROXY, + BLOB_KEY, + BLOB_KEY_PROXY, + BLOB_SSL_ISSUERCERT, + BLOB_SSL_ISSUERCERT_PROXY, + BLOB_CAINFO, + BLOB_CAINFO_PROXY, + BLOB_LAST +}; + +/* callback that gets called when this easy handle is completed within a multi + handle. Only used for internally created transfers, like for example + DoH. */ +typedef int (*multidone_func)(struct Curl_easy *easy, CURLcode result); + +struct UserDefined { + FILE *err; /* the stderr user data goes here */ + void *debugdata; /* the data that will be passed to fdebug */ + char *errorbuffer; /* (Static) store failure messages in here */ + void *out; /* CURLOPT_WRITEDATA */ + void *in_set; /* CURLOPT_READDATA */ + void *writeheader; /* write the header to this if non-NULL */ + unsigned short use_port; /* which port to use (when not using default) */ + unsigned long httpauth; /* kind of HTTP authentication to use (bitmask) */ + unsigned long proxyauth; /* kind of proxy authentication to use (bitmask) */ + long maxredirs; /* maximum no. of http(s) redirects to follow, set to -1 + for infinity */ + + void *postfields; /* if POST, set the fields' values here */ + 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 + (contain zero bytes) */ +#ifndef CURL_DISABLE_BINDLOCAL + unsigned short localport; /* local port number to bind to */ + unsigned short localportrange; /* number of additional port numbers to test + in case the 'localport' one can't be + bind()ed */ +#endif + curl_write_callback fwrite_func; /* function that stores the output */ + curl_write_callback fwrite_header; /* function that stores headers */ + curl_write_callback fwrite_rtp; /* function that stores interleaved RTP */ + curl_read_callback fread_func_set; /* function that reads the input */ + curl_progress_callback fprogress; /* OLD and deprecated progress callback */ + curl_xferinfo_callback fxferinfo; /* progress callback */ + curl_debug_callback fdebug; /* function that write informational data */ + curl_ioctl_callback ioctl_func; /* function for I/O control */ + curl_sockopt_callback fsockopt; /* function for setting socket options */ + void *sockopt_client; /* pointer to pass to the socket options callback */ + curl_opensocket_callback fopensocket; /* function for checking/translating + the address and opening the + socket */ + void *opensocket_client; + curl_closesocket_callback fclosesocket; /* function for closing the + socket */ + void *closesocket_client; + curl_prereq_callback fprereq; /* pre-initial request callback */ + void *prereq_userp; /* pre-initial request user data */ + + void *seek_client; /* pointer to pass to the seek callback */ +#ifndef CURL_DISABLE_HSTS + curl_hstsread_callback hsts_read; + void *hsts_read_userp; + curl_hstswrite_callback hsts_write; + void *hsts_write_userp; +#endif + void *progress_client; /* pointer to pass to the progress callback */ + void *ioctl_client; /* pointer to pass to the ioctl callback */ + unsigned int timeout; /* ms, 0 means no timeout */ + unsigned int connecttimeout; /* ms, 0 means no timeout */ + unsigned int happy_eyeballs_timeout; /* ms, 0 is a valid value */ + unsigned int server_response_timeout; /* ms, 0 means no timeout */ + long maxage_conn; /* in seconds, max idle time to allow a connection that + is to be reused */ + long maxlifetime_conn; /* in seconds, max time since creation to allow a + connection that is to be reused */ +#ifndef CURL_DISABLE_TFTP + long tftp_blksize; /* in bytes, 0 means use default */ +#endif + curl_off_t filesize; /* size of file to upload, -1 means unknown */ + long low_speed_limit; /* bytes/second */ + long low_speed_time; /* number of seconds */ + curl_off_t max_send_speed; /* high speed limit in bytes/second for upload */ + curl_off_t max_recv_speed; /* high speed limit in bytes/second for + download */ + curl_off_t set_resume_from; /* continue [ftp] transfer from here */ + struct curl_slist *headers; /* linked list of extra headers */ + struct curl_httppost *httppost; /* linked list of old POST data */ + curl_mimepart mimepost; /* MIME/POST data. */ +#ifndef CURL_DISABLE_TELNET + struct curl_slist *telnet_options; /* linked list of telnet options */ +#endif + struct curl_slist *resolve; /* list of names to add/remove from + DNS cache */ + struct curl_slist *connect_to; /* list of host:port mappings to override + the hostname and port to connect to */ + time_t timevalue; /* what time to compare with */ + unsigned char timecondition; /* kind of time comparison: curl_TimeCond */ + unsigned char method; /* what kind of HTTP request: Curl_HttpReq */ + unsigned char httpwant; /* when non-zero, a specific HTTP version requested + to be used in the library's request(s) */ + struct ssl_config_data ssl; /* user defined SSL stuff */ +#ifndef CURL_DISABLE_PROXY + struct ssl_config_data proxy_ssl; /* user defined SSL stuff for proxy */ + struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */ + unsigned short proxyport; /* If non-zero, use this port number by + default. If the proxy string features a + ":[port]" that one will override this. */ + unsigned char proxytype; /* what kind of proxy: curl_proxytype */ + unsigned char socks5auth;/* kind of SOCKS5 authentication to use (bitmask) */ +#endif + struct ssl_general_config general_ssl; /* general user defined SSL stuff */ + int dns_cache_timeout; /* DNS cache timeout (seconds) */ + unsigned int buffer_size; /* size of receive buffer to use */ + unsigned int upload_buffer_size; /* size of upload buffer to use, + keep it >= CURL_MAX_WRITE_SIZE */ + void *private_data; /* application-private data */ +#ifndef CURL_DISABLE_HTTP + struct curl_slist *http200aliases; /* linked list of aliases for http200 */ +#endif + unsigned char ipver; /* the CURL_IPRESOLVE_* defines in the public header + file 0 - whatever, 1 - v2, 2 - v6 */ + curl_off_t max_filesize; /* Maximum file size to download */ +#ifndef CURL_DISABLE_FTP + unsigned char ftp_filemethod; /* how to get to a file: curl_ftpfile */ + unsigned char ftpsslauth; /* what AUTH XXX to try: curl_ftpauth */ + unsigned char ftp_ccc; /* FTP CCC options: curl_ftpccc */ + unsigned int accepttimeout; /* in milliseconds, 0 means no timeout */ +#endif +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) + struct curl_slist *quote; /* after connection is established */ + struct curl_slist *postquote; /* after the transfer */ + struct curl_slist *prequote; /* before the transfer, after type */ + /* Despite the name, ftp_create_missing_dirs is for FTP(S) and SFTP + 1 - create directories that don't exist + 2 - the same but also allow MKD to fail once + */ + unsigned char ftp_create_missing_dirs; +#endif +#ifdef USE_LIBSSH2 + curl_sshhostkeycallback ssh_hostkeyfunc; /* hostkey check callback */ + void *ssh_hostkeyfunc_userp; /* custom pointer to callback */ +#endif +#ifdef USE_SSH + curl_sshkeycallback ssh_keyfunc; /* key matching callback */ + void *ssh_keyfunc_userp; /* custom pointer to callback */ + int ssh_auth_types; /* allowed SSH auth types */ + unsigned int new_directory_perms; /* when creating remote dirs */ +#endif +#ifndef CURL_DISABLE_NETRC + unsigned char use_netrc; /* enum CURL_NETRC_OPTION values */ +#endif + unsigned int new_file_perms; /* when creating remote files */ + char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */ + struct curl_blob *blobs[BLOB_LAST]; +#ifdef ENABLE_IPV6 + unsigned int scope_id; /* Scope id for IPv6 */ +#endif + curl_prot_t allowed_protocols; + 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 + starts */ + curl_chunk_end_callback chunk_end; /* called after part transferring + stopped */ + curl_fnmatch_callback fnmatch; /* callback to decide which file corresponds + to pattern (e.g. if WILDCARDMATCH is on) */ + void *fnmatch_data; + void *wildcardptr; +#endif + /* GSS-API credential delegation, see the documentation of + CURLOPT_GSSAPI_DELEGATION */ + unsigned char gssapi_delegation; + + int tcp_keepidle; /* seconds in idle before sending keepalive probe */ + int tcp_keepintvl; /* seconds between TCP keepalive probes */ + + long expect_100_timeout; /* in milliseconds */ +#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 */ + long upkeep_interval_ms; /* Time between calls for connection upkeep. */ + multidone_func fmultidone; +#ifndef CURL_DISABLE_DOH + struct Curl_easy *dohfor; /* this is a DoH request for that transfer */ +#endif + CURLU *uh; /* URL handle for the current parsed URL */ +#ifndef CURL_DISABLE_HTTP + void *trailer_data; /* pointer to pass to trailer data callback */ + curl_trailer_callback trailer_callback; /* trailing data callback */ +#endif + char keep_post; /* keep POSTs as POSTs after a 30x request; each + bit represents a request, from 301 to 303 */ +#ifndef CURL_DISABLE_SMTP + struct curl_slist *mail_rcpt; /* linked list of mail recipients */ + BIT(mail_rcpt_allowfails); /* allow RCPT TO command to fail for some + recipients */ +#endif + unsigned int maxconnects; /* Max idle connections in the connection cache */ + unsigned char use_ssl; /* if AUTH TLS is to be attempted etc, for FTP or + IMAP or POP3 or others! (type: curl_usessl)*/ + unsigned char connect_only; /* make connection/request, then let + application use the socket */ +#ifndef CURL_DISABLE_MIME + BIT(mime_formescape); +#endif + BIT(is_fread_set); /* has read callback been set to non-NULL? */ +#ifndef CURL_DISABLE_TFTP + BIT(tftp_no_options); /* do not send TFTP options requests */ +#endif + BIT(sep_headers); /* handle host and proxy headers separately */ +#ifndef CURL_DISABLE_COOKIES + BIT(cookiesession); /* new cookie session? */ +#endif + BIT(crlf); /* convert crlf on ftp upload(?) */ + BIT(ssh_compression); /* enable SSH compression */ + +/* Here follows boolean settings that define how to behave during + this session. They are STATIC, set by libcurl users or at least initially + and they don't change during operations. */ + BIT(quick_exit); /* set 1L when it is okay to leak things (like + threads), as we're about to exit() anyway and + don't want lengthy cleanups to delay termination, + e.g. after a DNS timeout */ + BIT(get_filetime); /* get the time and get of the remote file */ + BIT(tunnel_thru_httpproxy); /* use CONNECT through an HTTP proxy */ + BIT(prefer_ascii); /* ASCII rather than binary */ + BIT(remote_append); /* append, not overwrite, on upload */ +#ifdef CURL_LIST_ONLY_PROTOCOL + BIT(list_only); /* list directory */ +#endif +#ifndef CURL_DISABLE_FTP + BIT(ftp_use_port); /* use the FTP PORT command */ + BIT(ftp_use_epsv); /* if EPSV is to be attempted or not */ + BIT(ftp_use_eprt); /* if EPRT is to be attempted or not */ + BIT(ftp_use_pret); /* if PRET is to be used before PASV or not */ + BIT(ftp_skip_ip); /* skip the IP address the FTP server passes on to + us */ + BIT(wildcard_enabled); /* enable wildcard matching */ +#endif + BIT(hide_progress); /* don't use the progress meter */ + BIT(http_fail_on_error); /* fail on HTTP error codes >= 400 */ + BIT(http_keep_sending_on_error); /* for HTTP status codes >= 300 */ + BIT(http_follow_location); /* follow HTTP redirects */ + BIT(http_transfer_encoding); /* request compressed HTTP transfer-encoding */ + BIT(allow_auth_to_other_hosts); + BIT(include_header); /* include received protocol headers in data output */ + BIT(http_set_referer); /* is a custom referer used */ + BIT(http_auto_referer); /* set "correct" referer when following + location: */ + BIT(opt_no_body); /* as set with CURLOPT_NOBODY */ + BIT(verbose); /* output verbosity */ + BIT(krb); /* Kerberos connection requested */ + BIT(reuse_forbid); /* forbidden to be reused, close after use */ + BIT(reuse_fresh); /* do not reuse an existing connection */ + BIT(no_signal); /* do not use any signal/alarm handler */ + BIT(tcp_nodelay); /* whether to enable TCP_NODELAY or not */ + BIT(ignorecl); /* ignore content length */ + BIT(http_te_skip); /* pass the raw body data to the user, even when + transfer-encoded (chunked, compressed) */ + BIT(http_ce_skip); /* pass the raw body data to the user, even when + content-encoded (chunked, compressed) */ + BIT(proxy_transfer_mode); /* set transfer mode (;type=<a|i>) when doing + FTP via an HTTP proxy */ +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + BIT(socks5_gssapi_nec); /* Flag to support NEC SOCKS5 server */ +#endif + BIT(sasl_ir); /* Enable/disable SASL initial response */ + BIT(tcp_keepalive); /* use TCP keepalives */ + BIT(tcp_fastopen); /* use TCP Fast Open */ + BIT(ssl_enable_alpn);/* TLS ALPN extension? */ + BIT(path_as_is); /* allow dotdots? */ + BIT(pipewait); /* wait for multiplex status before starting a new + connection */ + BIT(suppress_connect_headers); /* suppress proxy CONNECT response headers + from user callbacks */ + BIT(dns_shuffle_addresses); /* whether to shuffle addresses before use */ + BIT(haproxyprotocol); /* whether to send HAProxy PROXY protocol v1 + header */ + BIT(abstract_unix_socket); + BIT(disallow_username_in_url); /* disallow username in url */ +#ifndef CURL_DISABLE_DOH + BIT(doh); /* DNS-over-HTTPS enabled */ + BIT(doh_verifypeer); /* DoH certificate peer verification */ + BIT(doh_verifyhost); /* DoH certificate hostname verification */ + BIT(doh_verifystatus); /* DoH certificate status verification */ +#endif + BIT(http09_allowed); /* allow HTTP/0.9 responses */ +#ifdef USE_WEBSOCKETS + BIT(ws_raw_mode); +#endif +}; + +struct Names { + struct Curl_hash *hostcache; + enum { + HCACHE_NONE, /* not pointing to anything */ + HCACHE_MULTI, /* points to a shared one in the multi handle */ + HCACHE_SHARED /* points to a shared one in a shared object */ + } hostcachetype; +}; + +/* + * The 'connectdata' struct MUST have all the connection oriented stuff as we + * may have several simultaneous connections and connection structs in memory. + * + * The 'struct UserDefined' must only contain data that is set once to go for + * many (perhaps) independent connections. Values that are generated or + * calculated internally for the "session handle" must be defined within the + * 'struct UrlState' instead. + */ + +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 */ + unsigned int magic; + /* once an easy handle is tied to a connection cache + a non-negative number to distinguish this transfer from + other using the same cache. For easier tracking + in log output. + This may wrap around after LONG_MAX to 0 again, so it + has no uniqueness guarantee for very large processings. */ + curl_off_t id; + + /* first, two fields for the linked list of these */ + struct Curl_easy *next; + struct Curl_easy *prev; + + struct connectdata *conn; + struct Curl_llist_element connect_queue; /* for the pending and msgsent + lists */ + struct Curl_llist_element conn_queue; /* list per connectdata */ + + CURLMstate mstate; /* the handle's state */ + CURLcode result; /* previous result */ + + struct Curl_message msg; /* A single posted message. */ + + /* Array with the plain socket numbers this handle takes care of, in no + particular order. Note that all sockets are added to the sockhash, where + the state etc are also kept. This array is mostly used to detect when a + socket is to be removed from the hash. See singlesocket(). */ + struct easy_pollset last_poll; + + struct Names dns; + 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_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 */ +#ifdef USE_LIBPSL + struct PslCache *psl; /* The associated PSL cache. */ +#endif + struct SingleRequest req; /* Request-specific data */ + struct UserDefined set; /* values set by the libcurl user */ +#ifndef CURL_DISABLE_COOKIES + struct CookieInfo *cookies; /* the cookies, read from files and servers. + NOTE that the 'cookie' field in the + UserDefined struct defines if the "engine" + is to be used or not. */ +#endif +#ifndef CURL_DISABLE_HSTS + struct hsts *hsts; +#endif +#ifndef CURL_DISABLE_ALTSVC + struct altsvcinfo *asi; /* the alt-svc cache */ +#endif + struct Progress progress; /* for all the progress meter data */ + struct UrlState state; /* struct for fields used for state info and + other dynamic purposes */ +#ifndef CURL_DISABLE_FTP + struct WildcardData *wildcard; /* wildcard download state info */ +#endif + struct PureInfo info; /* stats, reports and info data */ + struct curl_tlssessioninfo tsi; /* Information about the TLS session, only + valid after a client has asked for it */ +#ifdef USE_HYPER + struct hyptransfer hyp; +#endif +}; + +#define LIBCURL_NAME "libcurl" + +#endif /* HEADER_CURL_URLDATA_H */ diff --git a/Utilities/cmcurl/lib/vauth/cleartext.c b/Utilities/cmcurl/lib/vauth/cleartext.c new file mode 100644 index 0000000..972a874 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/cleartext.c @@ -0,0 +1,138 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC4616 PLAIN authentication + * Draft LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt> + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_POP3) || \ + (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)) + +#include <curl/curl.h> +#include "urldata.h" + +#include "vauth/vauth.h" +#include "warnless.h" +#include "strtok.h" +#include "sendf.h" +#include "curl_printf.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_auth_create_plain_message() + * + * This is used to generate an already encoded PLAIN message ready + * for sending to the recipient. + * + * Parameters: + * + * authzid [in] - The authorization identity. + * authcid [in] - The authentication identity. + * passwd [in] - The password. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_plain_message(const char *authzid, + const char *authcid, + const char *passwd, + struct bufref *out) +{ + char *plainauth; + size_t plainlen; + size_t zlen; + size_t clen; + size_t plen; + + zlen = (authzid == NULL ? 0 : strlen(authzid)); + clen = strlen(authcid); + plen = strlen(passwd); + + /* Compute binary message length. Check for overflows. */ + if((zlen > SIZE_T_MAX/4) || (clen > SIZE_T_MAX/4) || + (plen > (SIZE_T_MAX/2 - 2))) + return CURLE_OUT_OF_MEMORY; + plainlen = zlen + clen + plen + 2; + + plainauth = malloc(plainlen + 1); + if(!plainauth) + return CURLE_OUT_OF_MEMORY; + + /* Calculate the reply */ + if(zlen) + memcpy(plainauth, authzid, zlen); + plainauth[zlen] = '\0'; + memcpy(plainauth + zlen + 1, authcid, clen); + plainauth[zlen + clen + 1] = '\0'; + memcpy(plainauth + zlen + clen + 2, passwd, plen); + plainauth[plainlen] = '\0'; + Curl_bufref_set(out, plainauth, plainlen, curl_free); + return CURLE_OK; +} + +/* + * Curl_auth_create_login_message() + * + * This is used to generate an already encoded LOGIN message containing the + * user name or password ready for sending to the recipient. + * + * Parameters: + * + * valuep [in] - The user name or user's password. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_login_message(const char *valuep, struct bufref *out) +{ + Curl_bufref_set(out, valuep, strlen(valuep), NULL); + return CURLE_OK; +} + +/* + * Curl_auth_create_external_message() + * + * This is used to generate an already encoded EXTERNAL message containing + * the user name ready for sending to the recipient. + * + * Parameters: + * + * user [in] - The user name. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_external_message(const char *user, + struct bufref *out) +{ + /* This is the same formatting as the login message */ + return Curl_auth_create_login_message(user, out); +} + +#endif /* if no users */ diff --git a/Utilities/cmcurl/lib/vauth/cram.c b/Utilities/cmcurl/lib/vauth/cram.c new file mode 100644 index 0000000..91fb261 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/cram.c @@ -0,0 +1,97 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC2195 CRAM-MD5 authentication + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_DIGEST_AUTH + +#include <curl/curl.h> +#include "urldata.h" + +#include "vauth/vauth.h" +#include "curl_hmac.h" +#include "curl_md5.h" +#include "warnless.h" +#include "curl_printf.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + + +/* + * Curl_auth_create_cram_md5_message() + * + * This is used to generate a CRAM-MD5 response message ready for sending to + * the recipient. + * + * Parameters: + * + * chlg [in] - The challenge. + * userp [in] - The user name. + * passwdp [in] - The user's password. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_cram_md5_message(const struct bufref *chlg, + const char *userp, + const char *passwdp, + struct bufref *out) +{ + struct HMAC_context *ctxt; + unsigned char digest[MD5_DIGEST_LEN]; + char *response; + + /* Compute the digest using the password as the key */ + ctxt = Curl_HMAC_init(Curl_HMAC_MD5, + (const unsigned char *) passwdp, + curlx_uztoui(strlen(passwdp))); + if(!ctxt) + return CURLE_OUT_OF_MEMORY; + + /* Update the digest with the given challenge */ + if(Curl_bufref_len(chlg)) + Curl_HMAC_update(ctxt, Curl_bufref_ptr(chlg), + curlx_uztoui(Curl_bufref_len(chlg))); + + /* Finalise the digest */ + Curl_HMAC_final(ctxt, digest); + + /* Generate the response */ + response = aprintf( + "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + userp, digest[0], digest[1], digest[2], digest[3], digest[4], + digest[5], digest[6], digest[7], digest[8], digest[9], digest[10], + digest[11], digest[12], digest[13], digest[14], digest[15]); + if(!response) + return CURLE_OUT_OF_MEMORY; + + Curl_bufref_set(out, response, strlen(response), curl_free); + return CURLE_OK; +} + +#endif /* !CURL_DISABLE_DIGEST_AUTH */ diff --git a/Utilities/cmcurl/lib/vauth/digest.c b/Utilities/cmcurl/lib/vauth/digest.c new file mode 100644 index 0000000..416da0f --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/digest.c @@ -0,0 +1,994 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC2831 DIGEST-MD5 authentication + * RFC7616 DIGEST-SHA256, DIGEST-SHA512-256 authentication + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_DIGEST_AUTH + +#include <curl/curl.h> + +#include "vauth/vauth.h" +#include "vauth/digest.h" +#include "urldata.h" +#include "curl_base64.h" +#include "curl_hmac.h" +#include "curl_md5.h" +#include "curl_sha256.h" +#include "vtls/vtls.h" +#include "warnless.h" +#include "strtok.h" +#include "strcase.h" +#include "curl_printf.h" +#include "rand.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +#define SESSION_ALGO 1 /* for algos with this bit set */ + +#define ALGO_MD5 0 +#define ALGO_MD5SESS (ALGO_MD5 | SESSION_ALGO) +#define ALGO_SHA256 2 +#define ALGO_SHA256SESS (ALGO_SHA256 | SESSION_ALGO) +#define ALGO_SHA512_256 4 +#define ALGO_SHA512_256SESS (ALGO_SHA512_256 | SESSION_ALGO) + +#if !defined(USE_WINDOWS_SSPI) +#define DIGEST_QOP_VALUE_AUTH (1 << 0) +#define DIGEST_QOP_VALUE_AUTH_INT (1 << 1) +#define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2) + +#define DIGEST_QOP_VALUE_STRING_AUTH "auth" +#define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int" +#define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf" +#endif + +bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, + const char **endptr) +{ + int c; + bool starts_with_quote = FALSE; + bool escape = FALSE; + + for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);) + *value++ = *str++; + *value = 0; + + if('=' != *str++) + /* eek, no match */ + return FALSE; + + if('\"' == *str) { + /* This starts with a quote so it must end with one as well! */ + str++; + starts_with_quote = TRUE; + } + + for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) { + if(!escape) { + switch(*str) { + case '\\': + if(starts_with_quote) { + /* the start of an escaped quote */ + escape = TRUE; + continue; + } + break; + + case ',': + if(!starts_with_quote) { + /* This signals the end of the content if we didn't get a starting + quote and then we do "sloppy" parsing */ + c = 0; /* the end */ + continue; + } + break; + + case '\r': + case '\n': + /* end of string */ + if(starts_with_quote) + return FALSE; /* No closing quote */ + c = 0; + continue; + + case '\"': + if(starts_with_quote) { + /* end of string */ + c = 0; + continue; + } + else + return FALSE; + } + } + + escape = FALSE; + *content++ = *str; + } + if(escape) + return FALSE; /* No character after backslash */ + + *content = 0; + *endptr = str; + + return TRUE; +} + +#if !defined(USE_WINDOWS_SSPI) +/* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string */ +static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */ + unsigned char *dest) /* 33 bytes */ +{ + int i; + for(i = 0; i < 16; i++) + msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]); +} + +/* Convert sha256 chunk to RFC7616 -suitable ascii string */ +static void auth_digest_sha256_to_ascii(unsigned char *source, /* 32 bytes */ + unsigned char *dest) /* 65 bytes */ +{ + int i; + for(i = 0; i < 32; i++) + msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]); +} + +/* Perform quoted-string escaping as described in RFC2616 and its errata */ +static char *auth_digest_string_quoted(const char *source) +{ + char *dest; + const char *s = source; + size_t n = 1; /* null terminator */ + + /* Calculate size needed */ + while(*s) { + ++n; + if(*s == '"' || *s == '\\') { + ++n; + } + ++s; + } + + dest = malloc(n); + if(dest) { + char *d = dest; + s = source; + while(*s) { + if(*s == '"' || *s == '\\') { + *d++ = '\\'; + } + *d++ = *s++; + } + *d = '\0'; + } + + return dest; +} + +/* 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 + */ +static bool auth_digest_get_key_value(const char *chlg, + const char *key, + char *value, + size_t max_val_len, + char end_char) +{ + char *find_pos; + size_t i; + + find_pos = strstr(chlg, key); + if(!find_pos) + return FALSE; + + find_pos += strlen(key); + + for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i) + value[i] = *find_pos++; + value[i] = '\0'; + + return TRUE; +} + +static CURLcode auth_digest_get_qop_values(const char *options, int *value) +{ + char *tmp; + char *token; + char *tok_buf = NULL; + + /* Initialise the output */ + *value = 0; + + /* Tokenise the list of qop values. Use a temporary clone of the buffer since + strtok_r() ruins it. */ + tmp = strdup(options); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + + token = strtok_r(tmp, ",", &tok_buf); + while(token) { + if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) + *value |= DIGEST_QOP_VALUE_AUTH; + else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) + *value |= DIGEST_QOP_VALUE_AUTH_INT; + else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF)) + *value |= DIGEST_QOP_VALUE_AUTH_CONF; + + token = strtok_r(NULL, ",", &tok_buf); + } + + free(tmp); + + return CURLE_OK; +} + +/* + * auth_decode_digest_md5_message() + * + * This is used internally to decode an already encoded DIGEST-MD5 challenge + * message into the separate attributes. + * + * Parameters: + * + * chlgref [in] - The challenge message. + * nonce [in/out] - The buffer where the nonce will be stored. + * nlen [in] - The length of the nonce buffer. + * realm [in/out] - The buffer where the realm will be stored. + * rlen [in] - The length of the realm buffer. + * alg [in/out] - The buffer where the algorithm will be stored. + * alen [in] - The length of the algorithm buffer. + * qop [in/out] - The buffer where the qop-options will be stored. + * qlen [in] - The length of the qop buffer. + * + * Returns CURLE_OK on success. + */ +static CURLcode auth_decode_digest_md5_message(const struct bufref *chlgref, + char *nonce, size_t nlen, + char *realm, size_t rlen, + char *alg, size_t alen, + char *qop, size_t qlen) +{ + const char *chlg = (const char *) Curl_bufref_ptr(chlgref); + + /* Ensure we have a valid challenge message */ + if(!Curl_bufref_len(chlgref)) + return CURLE_BAD_CONTENT_ENCODING; + + /* Retrieve nonce string from the challenge */ + if(!auth_digest_get_key_value(chlg, "nonce=\"", nonce, nlen, '\"')) + return CURLE_BAD_CONTENT_ENCODING; + + /* Retrieve realm string from the challenge */ + if(!auth_digest_get_key_value(chlg, "realm=\"", realm, rlen, '\"')) { + /* Challenge does not have a realm, set empty string [RFC2831] page 6 */ + strcpy(realm, ""); + } + + /* Retrieve algorithm string from the challenge */ + if(!auth_digest_get_key_value(chlg, "algorithm=", alg, alen, ',')) + return CURLE_BAD_CONTENT_ENCODING; + + /* Retrieve qop-options string from the challenge */ + if(!auth_digest_get_key_value(chlg, "qop=\"", qop, qlen, '\"')) + return CURLE_BAD_CONTENT_ENCODING; + + return CURLE_OK; +} + +/* + * Curl_auth_is_digest_supported() + * + * This is used to evaluate if DIGEST is supported. + * + * Parameters: None + * + * Returns TRUE as DIGEST as handled by libcurl. + */ +bool Curl_auth_is_digest_supported(void) +{ + return TRUE; +} + +/* + * Curl_auth_create_digest_md5_message() + * + * This is used to generate an already encoded DIGEST-MD5 response message + * ready for sending to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * chlg [in] - The challenge message. + * userp [in] - The user name. + * passwdp [in] - The user's password. + * service [in] - The service type such as http, smtp, pop or imap. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, + const struct bufref *chlg, + const char *userp, + const char *passwdp, + const char *service, + struct bufref *out) +{ + size_t i; + struct MD5_context *ctxt; + char *response = NULL; + unsigned char digest[MD5_DIGEST_LEN]; + char HA1_hex[2 * MD5_DIGEST_LEN + 1]; + char HA2_hex[2 * MD5_DIGEST_LEN + 1]; + char resp_hash_hex[2 * MD5_DIGEST_LEN + 1]; + char nonce[64]; + char realm[128]; + char algorithm[64]; + char qop_options[64]; + int qop_values; + char cnonce[33]; + char nonceCount[] = "00000001"; + char method[] = "AUTHENTICATE"; + char qop[] = DIGEST_QOP_VALUE_STRING_AUTH; + char *spn = NULL; + + /* Decode the challenge message */ + CURLcode result = auth_decode_digest_md5_message(chlg, + nonce, sizeof(nonce), + realm, sizeof(realm), + algorithm, + sizeof(algorithm), + qop_options, + sizeof(qop_options)); + if(result) + return result; + + /* We only support md5 sessions */ + if(strcmp(algorithm, "md5-sess") != 0) + return CURLE_BAD_CONTENT_ENCODING; + + /* Get the qop-values from the qop-options */ + result = auth_digest_get_qop_values(qop_options, &qop_values); + if(result) + return result; + + /* We only support auth quality-of-protection */ + if(!(qop_values & DIGEST_QOP_VALUE_AUTH)) + return CURLE_BAD_CONTENT_ENCODING; + + /* Generate 32 random hex chars, 32 bytes + 1 null-termination */ + result = Curl_rand_hex(data, (unsigned char *)cnonce, sizeof(cnonce)); + if(result) + return result; + + /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */ + ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + if(!ctxt) + return CURLE_OUT_OF_MEMORY; + + Curl_MD5_update(ctxt, (const unsigned char *) userp, + curlx_uztoui(strlen(userp))); + Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); + Curl_MD5_update(ctxt, (const unsigned char *) realm, + curlx_uztoui(strlen(realm))); + Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); + Curl_MD5_update(ctxt, (const unsigned char *) passwdp, + curlx_uztoui(strlen(passwdp))); + Curl_MD5_final(ctxt, digest); + + ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + if(!ctxt) + return CURLE_OUT_OF_MEMORY; + + Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN); + Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); + Curl_MD5_update(ctxt, (const unsigned char *) nonce, + curlx_uztoui(strlen(nonce))); + Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); + Curl_MD5_update(ctxt, (const unsigned char *) cnonce, + curlx_uztoui(strlen(cnonce))); + Curl_MD5_final(ctxt, digest); + + /* Convert calculated 16 octet hex into 32 bytes string */ + for(i = 0; i < MD5_DIGEST_LEN; i++) + msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]); + + /* Generate our SPN */ + spn = Curl_auth_build_spn(service, data->conn->host.name, NULL); + if(!spn) + return CURLE_OUT_OF_MEMORY; + + /* Calculate H(A2) */ + ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + if(!ctxt) { + free(spn); + + return CURLE_OUT_OF_MEMORY; + } + + Curl_MD5_update(ctxt, (const unsigned char *) method, + curlx_uztoui(strlen(method))); + Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); + Curl_MD5_update(ctxt, (const unsigned char *) spn, + curlx_uztoui(strlen(spn))); + Curl_MD5_final(ctxt, digest); + + for(i = 0; i < MD5_DIGEST_LEN; i++) + msnprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]); + + /* Now calculate the response hash */ + ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + if(!ctxt) { + free(spn); + + return CURLE_OUT_OF_MEMORY; + } + + Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN); + Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); + Curl_MD5_update(ctxt, (const unsigned char *) nonce, + curlx_uztoui(strlen(nonce))); + Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); + + Curl_MD5_update(ctxt, (const unsigned char *) nonceCount, + curlx_uztoui(strlen(nonceCount))); + Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); + Curl_MD5_update(ctxt, (const unsigned char *) cnonce, + curlx_uztoui(strlen(cnonce))); + Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); + Curl_MD5_update(ctxt, (const unsigned char *) qop, + curlx_uztoui(strlen(qop))); + Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); + + Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN); + Curl_MD5_final(ctxt, digest); + + for(i = 0; i < MD5_DIGEST_LEN; i++) + msnprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]); + + /* Generate the response */ + response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\"," + "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s," + "qop=%s", + userp, realm, nonce, + cnonce, nonceCount, spn, resp_hash_hex, qop); + free(spn); + if(!response) + return CURLE_OUT_OF_MEMORY; + + /* Return the response. */ + Curl_bufref_set(out, response, strlen(response), curl_free); + return result; +} + +/* + * Curl_auth_decode_digest_http_message() + * + * This is used to decode an HTTP DIGEST challenge message into the separate + * attributes. + * + * Parameters: + * + * chlg [in] - The challenge message. + * digest [in/out] - The digest data struct being used and modified. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_decode_digest_http_message(const char *chlg, + struct digestdata *digest) +{ + bool before = FALSE; /* got a nonce before */ + bool foundAuth = FALSE; + bool foundAuthInt = FALSE; + char *token = NULL; + char *tmp = NULL; + + /* If we already have received a nonce, keep that in mind */ + if(digest->nonce) + before = TRUE; + + /* Clean up any former leftovers and initialise to defaults */ + Curl_auth_digest_cleanup(digest); + + for(;;) { + char value[DIGEST_MAX_VALUE_LENGTH]; + char content[DIGEST_MAX_CONTENT_LENGTH]; + + /* Pass all additional spaces here */ + while(*chlg && ISBLANK(*chlg)) + chlg++; + + /* Extract a value=content pair */ + if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) { + if(strcasecompare(value, "nonce")) { + free(digest->nonce); + digest->nonce = strdup(content); + if(!digest->nonce) + return CURLE_OUT_OF_MEMORY; + } + else if(strcasecompare(value, "stale")) { + if(strcasecompare(content, "true")) { + digest->stale = TRUE; + digest->nc = 1; /* we make a new nonce now */ + } + } + else if(strcasecompare(value, "realm")) { + free(digest->realm); + digest->realm = strdup(content); + if(!digest->realm) + return CURLE_OUT_OF_MEMORY; + } + else if(strcasecompare(value, "opaque")) { + free(digest->opaque); + digest->opaque = strdup(content); + if(!digest->opaque) + return CURLE_OUT_OF_MEMORY; + } + else if(strcasecompare(value, "qop")) { + char *tok_buf = NULL; + /* Tokenize the list and choose auth if possible, use a temporary + clone of the buffer since strtok_r() ruins it */ + tmp = strdup(content); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + + token = strtok_r(tmp, ",", &tok_buf); + while(token) { + /* Pass additional spaces here */ + while(*token && ISBLANK(*token)) + token++; + if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) { + foundAuth = TRUE; + } + else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) { + foundAuthInt = TRUE; + } + token = strtok_r(NULL, ",", &tok_buf); + } + + free(tmp); + + /* Select only auth or auth-int. Otherwise, ignore */ + if(foundAuth) { + free(digest->qop); + digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH); + if(!digest->qop) + return CURLE_OUT_OF_MEMORY; + } + else if(foundAuthInt) { + free(digest->qop); + digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT); + if(!digest->qop) + return CURLE_OUT_OF_MEMORY; + } + } + else if(strcasecompare(value, "algorithm")) { + free(digest->algorithm); + digest->algorithm = strdup(content); + if(!digest->algorithm) + return CURLE_OUT_OF_MEMORY; + + if(strcasecompare(content, "MD5-sess")) + digest->algo = ALGO_MD5SESS; + else if(strcasecompare(content, "MD5")) + digest->algo = ALGO_MD5; + else if(strcasecompare(content, "SHA-256")) + digest->algo = ALGO_SHA256; + else if(strcasecompare(content, "SHA-256-SESS")) + digest->algo = ALGO_SHA256SESS; + else if(strcasecompare(content, "SHA-512-256")) + digest->algo = ALGO_SHA512_256; + else if(strcasecompare(content, "SHA-512-256-SESS")) + digest->algo = ALGO_SHA512_256SESS; + else + return CURLE_BAD_CONTENT_ENCODING; + } + else if(strcasecompare(value, "userhash")) { + if(strcasecompare(content, "true")) { + digest->userhash = TRUE; + } + } + else { + /* Unknown specifier, ignore it! */ + } + } + else + break; /* We're done here */ + + /* Pass all additional spaces here */ + while(*chlg && ISBLANK(*chlg)) + chlg++; + + /* Allow the list to be comma-separated */ + if(',' == *chlg) + chlg++; + } + + /* We had a nonce since before, and we got another one now without + 'stale=true'. This means we provided bad credentials in the previous + request */ + if(before && !digest->stale) + return CURLE_BAD_CONTENT_ENCODING; + + /* We got this header without a nonce, that's a bad Digest line! */ + if(!digest->nonce) + return CURLE_BAD_CONTENT_ENCODING; + + /* "<algo>-sess" protocol versions require "auth" or "auth-int" qop */ + if(!digest->qop && (digest->algo & SESSION_ALGO)) + return CURLE_BAD_CONTENT_ENCODING; + + return CURLE_OK; +} + +/* + * auth_create_digest_http_message() + * + * This is used to generate an HTTP DIGEST response message ready for sending + * to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * userp [in] - The user name. + * passwdp [in] - The user's password. + * request [in] - The HTTP request. + * uripath [in] - The path of the HTTP uri. + * digest [in/out] - The digest data struct being used and modified. + * outptr [in/out] - The address where a pointer to newly allocated memory + * holding the result will be stored upon completion. + * outlen [out] - The length of the output message. + * + * Returns CURLE_OK on success. + */ +static CURLcode auth_create_digest_http_message( + struct Curl_easy *data, + const char *userp, + const char *passwdp, + const unsigned char *request, + const unsigned char *uripath, + struct digestdata *digest, + char **outptr, size_t *outlen, + void (*convert_to_ascii)(unsigned char *, unsigned char *), + CURLcode (*hash)(unsigned char *, const unsigned char *, + const size_t)) +{ + CURLcode result; + unsigned char hashbuf[32]; /* 32 bytes/256 bits */ + unsigned char request_digest[65]; + unsigned char ha1[65]; /* 64 digits and 1 zero byte */ + unsigned char ha2[65]; /* 64 digits and 1 zero byte */ + char userh[65]; + char *cnonce = NULL; + size_t cnonce_sz = 0; + char *userp_quoted; + char *realm_quoted; + char *nonce_quoted; + char *response = NULL; + char *hashthis = NULL; + char *tmp = NULL; + + memset(hashbuf, 0, sizeof(hashbuf)); + if(!digest->nc) + digest->nc = 1; + + if(!digest->cnonce) { + char cnoncebuf[33]; + result = Curl_rand_hex(data, (unsigned char *)cnoncebuf, + sizeof(cnoncebuf)); + if(result) + return result; + + result = Curl_base64_encode(cnoncebuf, strlen(cnoncebuf), + &cnonce, &cnonce_sz); + if(result) + return result; + + digest->cnonce = cnonce; + } + + if(digest->userhash) { + hashthis = aprintf("%s:%s", userp, digest->realm ? digest->realm : ""); + if(!hashthis) + return CURLE_OUT_OF_MEMORY; + + hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + free(hashthis); + convert_to_ascii(hashbuf, (unsigned char *)userh); + } + + /* + If the algorithm is "MD5" or unspecified (which then defaults to MD5): + + A1 = unq(username-value) ":" unq(realm-value) ":" passwd + + If the algorithm is "MD5-sess" then: + + A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":" + unq(nonce-value) ":" unq(cnonce-value) + */ + + hashthis = aprintf("%s:%s:%s", userp, digest->realm ? digest->realm : "", + passwdp); + if(!hashthis) + return CURLE_OUT_OF_MEMORY; + + hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + free(hashthis); + convert_to_ascii(hashbuf, ha1); + + if(digest->algo & SESSION_ALGO) { + /* nonce and cnonce are OUTSIDE the hash */ + tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + + hash(hashbuf, (unsigned char *) tmp, strlen(tmp)); + free(tmp); + convert_to_ascii(hashbuf, ha1); + } + + /* + If the "qop" directive's value is "auth" or is unspecified, then A2 is: + + A2 = Method ":" digest-uri-value + + If the "qop" value is "auth-int", then A2 is: + + 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) + */ + + hashthis = aprintf("%s:%s", request, uripath); + if(!hashthis) + return CURLE_OUT_OF_MEMORY; + + if(digest->qop && strcasecompare(digest->qop, "auth-int")) { + /* We don't support auth-int for PUT or POST */ + char hashed[65]; + char *hashthis2; + + hash(hashbuf, (const unsigned char *)"", 0); + convert_to_ascii(hashbuf, (unsigned char *)hashed); + + hashthis2 = aprintf("%s:%s", hashthis, hashed); + free(hashthis); + hashthis = hashthis2; + } + + if(!hashthis) + return CURLE_OUT_OF_MEMORY; + + hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + free(hashthis); + convert_to_ascii(hashbuf, ha2); + + if(digest->qop) { + hashthis = aprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc, + digest->cnonce, digest->qop, ha2); + } + else { + hashthis = aprintf("%s:%s:%s", ha1, digest->nonce, ha2); + } + + if(!hashthis) + return CURLE_OUT_OF_MEMORY; + + hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + free(hashthis); + convert_to_ascii(hashbuf, request_digest); + + /* For test case 64 (snooped from a Mozilla 1.3a request) + + Authorization: Digest username="testuser", realm="testrealm", \ + nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca" + + Digest parameters are all quoted strings. Username which is provided by + the user will need double quotes and backslashes within it escaped. + realm, nonce, and opaque will need backslashes as well as they were + de-escaped when copied from request header. cnonce is generated with + 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) + return CURLE_OUT_OF_MEMORY; + if(digest->realm) + realm_quoted = auth_digest_string_quoted(digest->realm); + else { + realm_quoted = malloc(1); + if(realm_quoted) + realm_quoted[0] = 0; + } + if(!realm_quoted) { + free(userp_quoted); + return CURLE_OUT_OF_MEMORY; + } + nonce_quoted = auth_digest_string_quoted(digest->nonce); + if(!nonce_quoted) { + free(realm_quoted); + free(userp_quoted); + return CURLE_OUT_OF_MEMORY; + } + + if(digest->qop) { + response = aprintf("username=\"%s\", " + "realm=\"%s\", " + "nonce=\"%s\", " + "uri=\"%s\", " + "cnonce=\"%s\", " + "nc=%08x, " + "qop=%s, " + "response=\"%s\"", + userp_quoted, + realm_quoted, + nonce_quoted, + uripath, + digest->cnonce, + digest->nc, + digest->qop, + request_digest); + + /* Increment nonce-count to use another nc value for the next request */ + digest->nc++; + } + else { + response = aprintf("username=\"%s\", " + "realm=\"%s\", " + "nonce=\"%s\", " + "uri=\"%s\", " + "response=\"%s\"", + userp_quoted, + realm_quoted, + nonce_quoted, + uripath, + request_digest); + } + free(nonce_quoted); + free(realm_quoted); + free(userp_quoted); + if(!response) + return CURLE_OUT_OF_MEMORY; + + /* Add the optional fields */ + if(digest->opaque) { + char *opaque_quoted; + /* Append the opaque */ + opaque_quoted = auth_digest_string_quoted(digest->opaque); + if(!opaque_quoted) { + free(response); + return CURLE_OUT_OF_MEMORY; + } + tmp = aprintf("%s, opaque=\"%s\"", response, opaque_quoted); + free(response); + free(opaque_quoted); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + + response = tmp; + } + + if(digest->algorithm) { + /* Append the algorithm */ + tmp = aprintf("%s, algorithm=%s", response, digest->algorithm); + free(response); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + + response = tmp; + } + + if(digest->userhash) { + /* Append the userhash */ + tmp = aprintf("%s, userhash=true", response); + free(response); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + + response = tmp; + } + + /* Return the output */ + *outptr = response; + *outlen = strlen(response); + + return CURLE_OK; +} + +/* + * Curl_auth_create_digest_http_message() + * + * This is used to generate an HTTP DIGEST response message ready for sending + * to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * userp [in] - The user name. + * passwdp [in] - The user's password. + * request [in] - The HTTP request. + * uripath [in] - The path of the HTTP uri. + * digest [in/out] - The digest data struct being used and modified. + * outptr [in/out] - The address where a pointer to newly allocated memory + * holding the result will be stored upon completion. + * outlen [out] - The length of the output message. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + const unsigned char *request, + const unsigned char *uripath, + struct digestdata *digest, + char **outptr, size_t *outlen) +{ + if(digest->algo <= ALGO_MD5SESS) + return auth_create_digest_http_message(data, userp, passwdp, + request, uripath, digest, + outptr, outlen, + auth_digest_md5_to_ascii, + Curl_md5it); + DEBUGASSERT(digest->algo <= ALGO_SHA512_256SESS); + return auth_create_digest_http_message(data, userp, passwdp, + request, uripath, digest, + outptr, outlen, + auth_digest_sha256_to_ascii, + Curl_sha256it); +} + +/* + * Curl_auth_digest_cleanup() + * + * This is used to clean up the digest specific data. + * + * Parameters: + * + * digest [in/out] - The digest data struct being cleaned up. + * + */ +void Curl_auth_digest_cleanup(struct digestdata *digest) +{ + Curl_safefree(digest->nonce); + Curl_safefree(digest->cnonce); + Curl_safefree(digest->realm); + Curl_safefree(digest->opaque); + Curl_safefree(digest->qop); + Curl_safefree(digest->algorithm); + + digest->nc = 0; + digest->algo = ALGO_MD5; /* default algorithm */ + digest->stale = FALSE; /* default means normal, not stale */ + digest->userhash = FALSE; +} +#endif /* !USE_WINDOWS_SSPI */ + +#endif /* !CURL_DISABLE_DIGEST_AUTH */ diff --git a/Utilities/cmcurl/lib/vauth/digest.h b/Utilities/cmcurl/lib/vauth/digest.h new file mode 100644 index 0000000..99ce1f9 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/digest.h @@ -0,0 +1,40 @@ +#ifndef HEADER_CURL_DIGEST_H +#define HEADER_CURL_DIGEST_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include <curl/curl.h> + +#ifndef CURL_DISABLE_DIGEST_AUTH + +#define DIGEST_MAX_VALUE_LENGTH 256 +#define DIGEST_MAX_CONTENT_LENGTH 1024 + +/* This is used to extract the realm from a challenge message */ +bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, + const char **endptr); + +#endif + +#endif /* HEADER_CURL_DIGEST_H */ diff --git a/Utilities/cmcurl/lib/vauth/digest_sspi.c b/Utilities/cmcurl/lib/vauth/digest_sspi.c new file mode 100644 index 0000000..02e36ea --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/digest_sspi.c @@ -0,0 +1,668 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC2831 DIGEST-MD5 authentication + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_WINDOWS_SSPI) && !defined(CURL_DISABLE_DIGEST_AUTH) + +#include <curl/curl.h> + +#include "vauth/vauth.h" +#include "vauth/digest.h" +#include "urldata.h" +#include "warnless.h" +#include "curl_multibyte.h" +#include "sendf.h" +#include "strdup.h" +#include "strcase.h" +#include "strerror.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* +* Curl_auth_is_digest_supported() +* +* This is used to evaluate if DIGEST is supported. +* +* Parameters: None +* +* Returns TRUE if DIGEST is supported by Windows SSPI. +*/ +bool Curl_auth_is_digest_supported(void) +{ + PSecPkgInfo SecurityPackage; + SECURITY_STATUS status; + + /* Query the security package for Digest */ + status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), + &SecurityPackage); + + /* Release the package buffer as it is not required anymore */ + if(status == SEC_E_OK) { + s_pSecFn->FreeContextBuffer(SecurityPackage); + } + + return (status == SEC_E_OK ? TRUE : FALSE); +} + +/* + * Curl_auth_create_digest_md5_message() + * + * This is used to generate an already encoded DIGEST-MD5 response message + * ready for sending to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * chlg [in] - The challenge message. + * userp [in] - The user name in the format User or Domain\User. + * passwdp [in] - The user's password. + * service [in] - The service type such as http, smtp, pop or imap. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, + const struct bufref *chlg, + const char *userp, + const char *passwdp, + const char *service, + struct bufref *out) +{ + CURLcode result = CURLE_OK; + TCHAR *spn = NULL; + size_t token_max = 0; + unsigned char *output_token = NULL; + CredHandle credentials; + CtxtHandle context; + PSecPkgInfo SecurityPackage; + SEC_WINNT_AUTH_IDENTITY identity; + SEC_WINNT_AUTH_IDENTITY *p_identity; + SecBuffer chlg_buf; + SecBuffer resp_buf; + SecBufferDesc chlg_desc; + SecBufferDesc resp_desc; + SECURITY_STATUS status; + unsigned long attrs; + TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ + + /* Ensure we have a valid challenge message */ + if(!Curl_bufref_len(chlg)) { + infof(data, "DIGEST-MD5 handshake failure (empty challenge message)"); + return CURLE_BAD_CONTENT_ENCODING; + } + + /* Query the security package for DigestSSP */ + status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), + &SecurityPackage); + if(status != SEC_E_OK) { + failf(data, "SSPI: couldn't get auth info"); + return CURLE_AUTH_ERROR; + } + + token_max = SecurityPackage->cbMaxToken; + + /* Release the package buffer as it is not required anymore */ + s_pSecFn->FreeContextBuffer(SecurityPackage); + + /* Allocate our response buffer */ + output_token = malloc(token_max); + if(!output_token) + return CURLE_OUT_OF_MEMORY; + + /* Generate our SPN */ + spn = Curl_auth_build_spn(service, data->conn->host.name, NULL); + if(!spn) { + free(output_token); + return CURLE_OUT_OF_MEMORY; + } + + if(userp && *userp) { + /* Populate our identity structure */ + result = Curl_create_sspi_identity(userp, passwdp, &identity); + if(result) { + free(spn); + free(output_token); + return result; + } + + /* Allow proper cleanup of the identity structure */ + p_identity = &identity; + } + else + /* Use the current Windows user */ + p_identity = NULL; + + /* Acquire our credentials handle */ + status = s_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *) TEXT(SP_NAME_DIGEST), + SECPKG_CRED_OUTBOUND, NULL, + p_identity, NULL, NULL, + &credentials, &expiry); + + if(status != SEC_E_OK) { + Curl_sspi_free_identity(p_identity); + free(spn); + free(output_token); + return CURLE_LOGIN_DENIED; + } + + /* Setup the challenge "input" security buffer */ + chlg_desc.ulVersion = SECBUFFER_VERSION; + chlg_desc.cBuffers = 1; + chlg_desc.pBuffers = &chlg_buf; + chlg_buf.BufferType = SECBUFFER_TOKEN; + chlg_buf.pvBuffer = (void *) Curl_bufref_ptr(chlg); + chlg_buf.cbBuffer = curlx_uztoul(Curl_bufref_len(chlg)); + + /* Setup the response "output" security buffer */ + resp_desc.ulVersion = SECBUFFER_VERSION; + resp_desc.cBuffers = 1; + resp_desc.pBuffers = &resp_buf; + resp_buf.BufferType = SECBUFFER_TOKEN; + resp_buf.pvBuffer = output_token; + resp_buf.cbBuffer = curlx_uztoul(token_max); + + /* Generate our response message */ + status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, + 0, 0, 0, &chlg_desc, 0, + &context, &resp_desc, &attrs, + &expiry); + + if(status == SEC_I_COMPLETE_NEEDED || + status == SEC_I_COMPLETE_AND_CONTINUE) + s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); + else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + char buffer[STRERROR_LEN]; +#endif + + s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_sspi_free_identity(p_identity); + free(spn); + free(output_token); + + if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + + infof(data, "schannel: InitializeSecurityContext failed: %s", + Curl_sspi_strerror(status, buffer, sizeof(buffer))); + + return CURLE_AUTH_ERROR; + } + + /* Return the response. */ + Curl_bufref_set(out, output_token, resp_buf.cbBuffer, curl_free); + + /* Free our handles */ + s_pSecFn->DeleteSecurityContext(&context); + s_pSecFn->FreeCredentialsHandle(&credentials); + + /* Free the identity structure */ + Curl_sspi_free_identity(p_identity); + + /* Free the SPN */ + free(spn); + + return result; +} + +/* + * Curl_override_sspi_http_realm() + * + * This is used to populate the domain in a SSPI identity structure + * The realm is extracted from the challenge message and used as the + * domain if it is not already explicitly set. + * + * Parameters: + * + * chlg [in] - The challenge message. + * identity [in/out] - The identity structure. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_override_sspi_http_realm(const char *chlg, + SEC_WINNT_AUTH_IDENTITY *identity) +{ + xcharp_u domain, dup_domain; + + /* If domain is blank or unset, check challenge message for realm */ + if(!identity->Domain || !identity->DomainLength) { + for(;;) { + char value[DIGEST_MAX_VALUE_LENGTH]; + char content[DIGEST_MAX_CONTENT_LENGTH]; + + /* Pass all additional spaces here */ + while(*chlg && ISBLANK(*chlg)) + chlg++; + + /* Extract a value=content pair */ + if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) { + if(strcasecompare(value, "realm")) { + + /* Setup identity's domain and length */ + domain.tchar_ptr = curlx_convert_UTF8_to_tchar((char *) content); + if(!domain.tchar_ptr) + return CURLE_OUT_OF_MEMORY; + + dup_domain.tchar_ptr = _tcsdup(domain.tchar_ptr); + if(!dup_domain.tchar_ptr) { + curlx_unicodefree(domain.tchar_ptr); + return CURLE_OUT_OF_MEMORY; + } + + free(identity->Domain); + identity->Domain = dup_domain.tbyte_ptr; + identity->DomainLength = curlx_uztoul(_tcslen(dup_domain.tchar_ptr)); + dup_domain.tchar_ptr = NULL; + + curlx_unicodefree(domain.tchar_ptr); + } + else { + /* Unknown specifier, ignore it! */ + } + } + else + break; /* We're done here */ + + /* Pass all additional spaces here */ + while(*chlg && ISBLANK(*chlg)) + chlg++; + + /* Allow the list to be comma-separated */ + if(',' == *chlg) + chlg++; + } + } + + return CURLE_OK; +} + +/* + * Curl_auth_decode_digest_http_message() + * + * This is used to decode an HTTP DIGEST challenge message into the separate + * attributes. + * + * Parameters: + * + * chlg [in] - The challenge message. + * digest [in/out] - The digest data struct being used and modified. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_decode_digest_http_message(const char *chlg, + struct digestdata *digest) +{ + size_t chlglen = strlen(chlg); + + /* We had an input token before so if there's another one now that means we + provided bad credentials in the previous request or it's stale. */ + if(digest->input_token) { + bool stale = false; + const char *p = chlg; + + /* Check for the 'stale' directive */ + for(;;) { + char value[DIGEST_MAX_VALUE_LENGTH]; + char content[DIGEST_MAX_CONTENT_LENGTH]; + + while(*p && ISBLANK(*p)) + p++; + + if(!Curl_auth_digest_get_pair(p, value, content, &p)) + break; + + if(strcasecompare(value, "stale") && + strcasecompare(content, "true")) { + stale = true; + break; + } + + while(*p && ISBLANK(*p)) + p++; + + if(',' == *p) + p++; + } + + if(stale) + Curl_auth_digest_cleanup(digest); + else + return CURLE_LOGIN_DENIED; + } + + /* Store the challenge for use later */ + digest->input_token = (BYTE *) Curl_memdup(chlg, chlglen + 1); + if(!digest->input_token) + return CURLE_OUT_OF_MEMORY; + + digest->input_token_len = chlglen; + + return CURLE_OK; +} + +/* + * Curl_auth_create_digest_http_message() + * + * This is used to generate an HTTP DIGEST response message ready for sending + * to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * userp [in] - The user name in the format User or Domain\User. + * passwdp [in] - The user's password. + * request [in] - The HTTP request. + * uripath [in] - The path of the HTTP uri. + * digest [in/out] - The digest data struct being used and modified. + * outptr [in/out] - The address where a pointer to newly allocated memory + * holding the result will be stored upon completion. + * outlen [out] - The length of the output message. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + const unsigned char *request, + const unsigned char *uripath, + struct digestdata *digest, + char **outptr, size_t *outlen) +{ + size_t token_max; + char *resp; + BYTE *output_token; + size_t output_token_len = 0; + PSecPkgInfo SecurityPackage; + SecBuffer chlg_buf[5]; + SecBufferDesc chlg_desc; + SECURITY_STATUS status; + + (void) data; + + /* Query the security package for DigestSSP */ + status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), + &SecurityPackage); + if(status != SEC_E_OK) { + failf(data, "SSPI: couldn't get auth info"); + return CURLE_AUTH_ERROR; + } + + token_max = SecurityPackage->cbMaxToken; + + /* Release the package buffer as it is not required anymore */ + s_pSecFn->FreeContextBuffer(SecurityPackage); + + /* Allocate the output buffer according to the max token size as indicated + by the security package */ + output_token = malloc(token_max); + if(!output_token) { + return CURLE_OUT_OF_MEMORY; + } + + /* If the user/passwd that was used to make the identity for http_context + has changed then delete that context. */ + if((userp && !digest->user) || (!userp && digest->user) || + (passwdp && !digest->passwd) || (!passwdp && digest->passwd) || + (userp && digest->user && Curl_timestrcmp(userp, digest->user)) || + (passwdp && digest->passwd && Curl_timestrcmp(passwdp, digest->passwd))) { + if(digest->http_context) { + s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_safefree(digest->http_context); + } + Curl_safefree(digest->user); + Curl_safefree(digest->passwd); + } + + if(digest->http_context) { + chlg_desc.ulVersion = SECBUFFER_VERSION; + chlg_desc.cBuffers = 5; + chlg_desc.pBuffers = chlg_buf; + chlg_buf[0].BufferType = SECBUFFER_TOKEN; + chlg_buf[0].pvBuffer = NULL; + chlg_buf[0].cbBuffer = 0; + chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; + chlg_buf[1].pvBuffer = (void *) request; + chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); + chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; + chlg_buf[2].pvBuffer = (void *) uripath; + chlg_buf[2].cbBuffer = curlx_uztoul(strlen((const char *) uripath)); + chlg_buf[3].BufferType = SECBUFFER_PKG_PARAMS; + chlg_buf[3].pvBuffer = NULL; + chlg_buf[3].cbBuffer = 0; + chlg_buf[4].BufferType = SECBUFFER_PADDING; + chlg_buf[4].pvBuffer = output_token; + chlg_buf[4].cbBuffer = curlx_uztoul(token_max); + + status = s_pSecFn->MakeSignature(digest->http_context, 0, &chlg_desc, 0); + if(status == SEC_E_OK) + output_token_len = chlg_buf[4].cbBuffer; + else { /* delete the context so a new one can be made */ + infof(data, "digest_sspi: MakeSignature failed, error 0x%08lx", + (long)status); + s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_safefree(digest->http_context); + } + } + + if(!digest->http_context) { + CredHandle credentials; + SEC_WINNT_AUTH_IDENTITY identity; + SEC_WINNT_AUTH_IDENTITY *p_identity; + SecBuffer resp_buf; + SecBufferDesc resp_desc; + unsigned long attrs; + TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ + TCHAR *spn; + + /* free the copy of user/passwd used to make the previous identity */ + Curl_safefree(digest->user); + Curl_safefree(digest->passwd); + + if(userp && *userp) { + /* Populate our identity structure */ + if(Curl_create_sspi_identity(userp, passwdp, &identity)) { + free(output_token); + return CURLE_OUT_OF_MEMORY; + } + + /* Populate our identity domain */ + if(Curl_override_sspi_http_realm((const char *) digest->input_token, + &identity)) { + free(output_token); + return CURLE_OUT_OF_MEMORY; + } + + /* Allow proper cleanup of the identity structure */ + p_identity = &identity; + } + else + /* Use the current Windows user */ + p_identity = NULL; + + if(userp) { + digest->user = strdup(userp); + + if(!digest->user) { + free(output_token); + return CURLE_OUT_OF_MEMORY; + } + } + + if(passwdp) { + digest->passwd = strdup(passwdp); + + if(!digest->passwd) { + free(output_token); + Curl_safefree(digest->user); + return CURLE_OUT_OF_MEMORY; + } + } + + /* Acquire our credentials handle */ + status = s_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *) TEXT(SP_NAME_DIGEST), + SECPKG_CRED_OUTBOUND, NULL, + p_identity, NULL, NULL, + &credentials, &expiry); + if(status != SEC_E_OK) { + Curl_sspi_free_identity(p_identity); + free(output_token); + + return CURLE_LOGIN_DENIED; + } + + /* Setup the challenge "input" security buffer if present */ + chlg_desc.ulVersion = SECBUFFER_VERSION; + chlg_desc.cBuffers = 3; + chlg_desc.pBuffers = chlg_buf; + chlg_buf[0].BufferType = SECBUFFER_TOKEN; + chlg_buf[0].pvBuffer = digest->input_token; + chlg_buf[0].cbBuffer = curlx_uztoul(digest->input_token_len); + chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; + chlg_buf[1].pvBuffer = (void *) request; + chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); + chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; + chlg_buf[2].pvBuffer = NULL; + chlg_buf[2].cbBuffer = 0; + + /* Setup the response "output" security buffer */ + resp_desc.ulVersion = SECBUFFER_VERSION; + resp_desc.cBuffers = 1; + resp_desc.pBuffers = &resp_buf; + resp_buf.BufferType = SECBUFFER_TOKEN; + resp_buf.pvBuffer = output_token; + resp_buf.cbBuffer = curlx_uztoul(token_max); + + spn = curlx_convert_UTF8_to_tchar((char *) uripath); + if(!spn) { + s_pSecFn->FreeCredentialsHandle(&credentials); + + Curl_sspi_free_identity(p_identity); + free(output_token); + + return CURLE_OUT_OF_MEMORY; + } + + /* Allocate our new context handle */ + digest->http_context = calloc(1, sizeof(CtxtHandle)); + if(!digest->http_context) + return CURLE_OUT_OF_MEMORY; + + /* Generate our response message */ + status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, + spn, + ISC_REQ_USE_HTTP_STYLE, 0, 0, + &chlg_desc, 0, + digest->http_context, + &resp_desc, &attrs, &expiry); + curlx_unicodefree(spn); + + if(status == SEC_I_COMPLETE_NEEDED || + status == SEC_I_COMPLETE_AND_CONTINUE) + s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); + else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + char buffer[STRERROR_LEN]; +#endif + + s_pSecFn->FreeCredentialsHandle(&credentials); + + Curl_sspi_free_identity(p_identity); + free(output_token); + + Curl_safefree(digest->http_context); + + if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + + infof(data, "schannel: InitializeSecurityContext failed: %s", + Curl_sspi_strerror(status, buffer, sizeof(buffer))); + + return CURLE_AUTH_ERROR; + } + + output_token_len = resp_buf.cbBuffer; + + s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_sspi_free_identity(p_identity); + } + + resp = malloc(output_token_len + 1); + if(!resp) { + free(output_token); + + return CURLE_OUT_OF_MEMORY; + } + + /* Copy the generated response */ + memcpy(resp, output_token, output_token_len); + resp[output_token_len] = 0; + + /* Return the response */ + *outptr = resp; + *outlen = output_token_len; + + /* Free the response buffer */ + free(output_token); + + return CURLE_OK; +} + +/* + * Curl_auth_digest_cleanup() + * + * This is used to clean up the digest specific data. + * + * Parameters: + * + * digest [in/out] - The digest data struct being cleaned up. + * + */ +void Curl_auth_digest_cleanup(struct digestdata *digest) +{ + /* Free the input token */ + Curl_safefree(digest->input_token); + + /* Reset any variables */ + digest->input_token_len = 0; + + /* Delete security context */ + if(digest->http_context) { + s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_safefree(digest->http_context); + } + + /* Free the copy of user/passwd used to make the identity for http_context */ + Curl_safefree(digest->user); + Curl_safefree(digest->passwd); +} + +#endif /* USE_WINDOWS_SSPI && !CURL_DISABLE_DIGEST_AUTH */ diff --git a/Utilities/cmcurl/lib/vauth/gsasl.c b/Utilities/cmcurl/lib/vauth/gsasl.c new file mode 100644 index 0000000..c7d0a8d --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/gsasl.c @@ -0,0 +1,127 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Simon Josefsson, <simon@josefsson.org>, 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 + * + * RFC5802 SCRAM-SHA-1 authentication + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_GSASL + +#include <curl/curl.h> + +#include "vauth/vauth.h" +#include "urldata.h" +#include "sendf.h" + +#include <gsasl.h> + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, + const char *mech, + struct gsasldata *gsasl) +{ + int res; + + res = gsasl_init(&gsasl->ctx); + if(res != GSASL_OK) { + failf(data, "gsasl init: %s\n", gsasl_strerror(res)); + return FALSE; + } + + res = gsasl_client_start(gsasl->ctx, mech, &gsasl->client); + if(res != GSASL_OK) { + gsasl_done(gsasl->ctx); + return FALSE; + } + + return true; +} + +CURLcode Curl_auth_gsasl_start(struct Curl_easy *data, + const char *userp, + const char *passwdp, + struct gsasldata *gsasl) +{ +#if GSASL_VERSION_NUMBER >= 0x010b00 + int res; + res = +#endif + gsasl_property_set(gsasl->client, GSASL_AUTHID, userp); +#if GSASL_VERSION_NUMBER >= 0x010b00 + if(res != GSASL_OK) { + failf(data, "setting AUTHID failed: %s\n", gsasl_strerror(res)); + return CURLE_OUT_OF_MEMORY; + } +#endif + +#if GSASL_VERSION_NUMBER >= 0x010b00 + res = +#endif + gsasl_property_set(gsasl->client, GSASL_PASSWORD, passwdp); +#if GSASL_VERSION_NUMBER >= 0x010b00 + if(res != GSASL_OK) { + failf(data, "setting PASSWORD failed: %s\n", gsasl_strerror(res)); + return CURLE_OUT_OF_MEMORY; + } +#endif + + (void)data; + + return CURLE_OK; +} + +CURLcode Curl_auth_gsasl_token(struct Curl_easy *data, + const struct bufref *chlg, + struct gsasldata *gsasl, + struct bufref *out) +{ + int res; + char *response; + size_t outlen; + + res = gsasl_step(gsasl->client, + (const char *) Curl_bufref_ptr(chlg), Curl_bufref_len(chlg), + &response, &outlen); + if(res != GSASL_OK && res != GSASL_NEEDS_MORE) { + failf(data, "GSASL step: %s\n", gsasl_strerror(res)); + return CURLE_BAD_CONTENT_ENCODING; + } + + Curl_bufref_set(out, response, outlen, gsasl_free); + return CURLE_OK; +} + +void Curl_auth_gsasl_cleanup(struct gsasldata *gsasl) +{ + gsasl_finish(gsasl->client); + gsasl->client = NULL; + + gsasl_done(gsasl->ctx); + gsasl->ctx = NULL; +} +#endif diff --git a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c new file mode 100644 index 0000000..65eb3e1 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c @@ -0,0 +1,323 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC4752 The Kerberos V5 ("GSSAPI") SASL Mechanism + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(HAVE_GSSAPI) && defined(USE_KERBEROS5) + +#include <curl/curl.h> + +#include "vauth/vauth.h" +#include "curl_sasl.h" +#include "urldata.h" +#include "curl_gssapi.h" +#include "sendf.h" +#include "curl_printf.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_auth_is_gssapi_supported() + * + * This is used to evaluate if GSSAPI (Kerberos V5) is supported. + * + * Parameters: None + * + * Returns TRUE if Kerberos V5 is supported by the GSS-API library. + */ +bool Curl_auth_is_gssapi_supported(void) +{ + return TRUE; +} + +/* + * Curl_auth_create_gssapi_user_message() + * + * This is used to generate an already encoded GSSAPI (Kerberos V5) user token + * message ready for sending to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * userp [in] - The user name. + * passwdp [in] - The user's password. + * service [in] - The service type such as http, smtp, pop or imap. + * host [in[ - The host name. + * mutual_auth [in] - Flag specifying whether or not mutual authentication + * is enabled. + * chlg [in] - Optional challenge message. + * krb5 [in/out] - The Kerberos 5 data struct being used and modified. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + const char *service, + const char *host, + const bool mutual_auth, + const struct bufref *chlg, + struct kerberos5data *krb5, + struct bufref *out) +{ + CURLcode result = CURLE_OK; + OM_uint32 major_status; + OM_uint32 minor_status; + OM_uint32 unused_status; + gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + + (void) userp; + (void) passwdp; + + if(!krb5->spn) { + /* Generate our SPN */ + char *spn = Curl_auth_build_spn(service, NULL, host); + if(!spn) + return CURLE_OUT_OF_MEMORY; + + /* Populate the SPN structure */ + spn_token.value = spn; + spn_token.length = strlen(spn); + + /* Import the SPN */ + major_status = gss_import_name(&minor_status, &spn_token, + GSS_C_NT_HOSTBASED_SERVICE, &krb5->spn); + if(GSS_ERROR(major_status)) { + Curl_gss_log_error(data, "gss_import_name() failed: ", + major_status, minor_status); + + free(spn); + + return CURLE_AUTH_ERROR; + } + + free(spn); + } + + if(chlg) { + if(!Curl_bufref_len(chlg)) { + infof(data, "GSSAPI handshake failure (empty challenge message)"); + return CURLE_BAD_CONTENT_ENCODING; + } + input_token.value = (void *) Curl_bufref_ptr(chlg); + input_token.length = Curl_bufref_len(chlg); + } + + major_status = Curl_gss_init_sec_context(data, + &minor_status, + &krb5->context, + krb5->spn, + &Curl_krb5_mech_oid, + GSS_C_NO_CHANNEL_BINDINGS, + &input_token, + &output_token, + mutual_auth, + NULL); + + if(GSS_ERROR(major_status)) { + if(output_token.value) + gss_release_buffer(&unused_status, &output_token); + + Curl_gss_log_error(data, "gss_init_sec_context() failed: ", + major_status, minor_status); + + return CURLE_AUTH_ERROR; + } + + if(output_token.value && output_token.length) { + result = Curl_bufref_memdup(out, output_token.value, output_token.length); + gss_release_buffer(&unused_status, &output_token); + } + else + Curl_bufref_set(out, mutual_auth? "": NULL, 0, NULL); + + return result; +} + +/* + * Curl_auth_create_gssapi_security_message() + * + * This is used to generate an already encoded GSSAPI (Kerberos V5) security + * token message ready for sending to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * authzid [in] - The authorization identity if some. + * chlg [in] - Optional challenge message. + * krb5 [in/out] - The Kerberos 5 data struct being used and modified. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, + const char *authzid, + const struct bufref *chlg, + struct kerberos5data *krb5, + struct bufref *out) +{ + CURLcode result = CURLE_OK; + size_t messagelen = 0; + unsigned char *message = NULL; + OM_uint32 major_status; + OM_uint32 minor_status; + OM_uint32 unused_status; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + unsigned char *indata; + gss_qop_t qop = GSS_C_QOP_DEFAULT; + unsigned int sec_layer = 0; + unsigned int max_size = 0; + + /* Ensure we have a valid challenge message */ + if(!Curl_bufref_len(chlg)) { + infof(data, "GSSAPI handshake failure (empty security message)"); + return CURLE_BAD_CONTENT_ENCODING; + } + + /* Setup the challenge "input" security buffer */ + input_token.value = (void *) Curl_bufref_ptr(chlg); + input_token.length = Curl_bufref_len(chlg); + + /* Decrypt the inbound challenge and obtain the qop */ + major_status = gss_unwrap(&minor_status, krb5->context, &input_token, + &output_token, NULL, &qop); + if(GSS_ERROR(major_status)) { + Curl_gss_log_error(data, "gss_unwrap() failed: ", + major_status, minor_status); + return CURLE_BAD_CONTENT_ENCODING; + } + + /* Not 4 octets long so fail as per RFC4752 Section 3.1 */ + if(output_token.length != 4) { + infof(data, "GSSAPI handshake failure (invalid security data)"); + return CURLE_BAD_CONTENT_ENCODING; + } + + /* Extract the security layer and the maximum message size */ + indata = output_token.value; + sec_layer = indata[0]; + max_size = (indata[1] << 16) | (indata[2] << 8) | indata[3]; + + /* Free the challenge as it is not required anymore */ + gss_release_buffer(&unused_status, &output_token); + + /* Process the security layer */ + if(!(sec_layer & GSSAUTH_P_NONE)) { + infof(data, "GSSAPI handshake failure (invalid security layer)"); + + return CURLE_BAD_CONTENT_ENCODING; + } + sec_layer &= GSSAUTH_P_NONE; /* We do not support a security layer */ + + /* Process the maximum message size the server can receive */ + if(max_size > 0) { + /* The server has told us it supports a maximum receive buffer, however, as + we don't require one unless we are encrypting data, we tell the server + our receive buffer is zero. */ + max_size = 0; + } + + /* Allocate our message */ + messagelen = 4; + if(authzid) + messagelen += strlen(authzid); + message = malloc(messagelen); + if(!message) + return CURLE_OUT_OF_MEMORY; + + /* Populate the message with the security layer and client supported receive + message size. */ + message[0] = sec_layer & 0xFF; + message[1] = (max_size >> 16) & 0xFF; + message[2] = (max_size >> 8) & 0xFF; + message[3] = max_size & 0xFF; + + /* If given, append the authorization identity. */ + + if(authzid && *authzid) + memcpy(message + 4, authzid, messagelen - 4); + + /* Setup the "authentication data" security buffer */ + input_token.value = message; + input_token.length = messagelen; + + /* Encrypt the data */ + major_status = gss_wrap(&minor_status, krb5->context, 0, + GSS_C_QOP_DEFAULT, &input_token, NULL, + &output_token); + if(GSS_ERROR(major_status)) { + Curl_gss_log_error(data, "gss_wrap() failed: ", + major_status, minor_status); + free(message); + return CURLE_AUTH_ERROR; + } + + /* Return the response. */ + result = Curl_bufref_memdup(out, output_token.value, output_token.length); + /* Free the output buffer */ + gss_release_buffer(&unused_status, &output_token); + + /* Free the message buffer */ + free(message); + + return result; +} + +/* + * Curl_auth_cleanup_gssapi() + * + * This is used to clean up the GSSAPI (Kerberos V5) specific data. + * + * Parameters: + * + * krb5 [in/out] - The Kerberos 5 data struct being cleaned up. + * + */ +void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5) +{ + OM_uint32 minor_status; + + /* Free our security context */ + if(krb5->context != GSS_C_NO_CONTEXT) { + gss_delete_sec_context(&minor_status, &krb5->context, GSS_C_NO_BUFFER); + krb5->context = GSS_C_NO_CONTEXT; + } + + /* Free the SPN */ + if(krb5->spn != GSS_C_NO_NAME) { + gss_release_name(&minor_status, &krb5->spn); + krb5->spn = GSS_C_NO_NAME; + } +} + +#endif /* HAVE_GSSAPI && USE_KERBEROS5 */ diff --git a/Utilities/cmcurl/lib/vauth/krb5_sspi.c b/Utilities/cmcurl/lib/vauth/krb5_sspi.c new file mode 100644 index 0000000..c487149 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/krb5_sspi.c @@ -0,0 +1,474 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * + * 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 + * + * RFC4752 The Kerberos V5 ("GSSAPI") SASL Mechanism + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_WINDOWS_SSPI) && defined(USE_KERBEROS5) + +#include <curl/curl.h> + +#include "vauth/vauth.h" +#include "urldata.h" +#include "warnless.h" +#include "curl_multibyte.h" +#include "sendf.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_auth_is_gssapi_supported() + * + * This is used to evaluate if GSSAPI (Kerberos V5) is supported. + * + * Parameters: None + * + * Returns TRUE if Kerberos V5 is supported by Windows SSPI. + */ +bool Curl_auth_is_gssapi_supported(void) +{ + PSecPkgInfo SecurityPackage; + SECURITY_STATUS status; + + /* Query the security package for Kerberos */ + status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) + TEXT(SP_NAME_KERBEROS), + &SecurityPackage); + + /* Release the package buffer as it is not required anymore */ + if(status == SEC_E_OK) { + s_pSecFn->FreeContextBuffer(SecurityPackage); + } + + return (status == SEC_E_OK ? TRUE : FALSE); +} + +/* + * Curl_auth_create_gssapi_user_message() + * + * This is used to generate an already encoded GSSAPI (Kerberos V5) user token + * message ready for sending to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * userp [in] - The user name in the format User or Domain\User. + * passwdp [in] - The user's password. + * service [in] - The service type such as http, smtp, pop or imap. + * host [in] - The host name. + * mutual_auth [in] - Flag specifying whether or not mutual authentication + * is enabled. + * chlg [in] - Optional challenge message. + * krb5 [in/out] - The Kerberos 5 data struct being used and modified. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + const char *service, + const char *host, + const bool mutual_auth, + const struct bufref *chlg, + struct kerberos5data *krb5, + struct bufref *out) +{ + CURLcode result = CURLE_OK; + CtxtHandle context; + PSecPkgInfo SecurityPackage; + SecBuffer chlg_buf; + SecBuffer resp_buf; + SecBufferDesc chlg_desc; + SecBufferDesc resp_desc; + SECURITY_STATUS status; + unsigned long attrs; + TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ + + if(!krb5->spn) { + /* Generate our SPN */ + krb5->spn = Curl_auth_build_spn(service, host, NULL); + if(!krb5->spn) + return CURLE_OUT_OF_MEMORY; + } + + if(!krb5->output_token) { + /* Query the security package for Kerberos */ + status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) + TEXT(SP_NAME_KERBEROS), + &SecurityPackage); + if(status != SEC_E_OK) { + failf(data, "SSPI: couldn't get auth info"); + return CURLE_AUTH_ERROR; + } + + krb5->token_max = SecurityPackage->cbMaxToken; + + /* Release the package buffer as it is not required anymore */ + s_pSecFn->FreeContextBuffer(SecurityPackage); + + /* Allocate our response buffer */ + krb5->output_token = malloc(krb5->token_max); + if(!krb5->output_token) + return CURLE_OUT_OF_MEMORY; + } + + if(!krb5->credentials) { + /* Do we have credentials to use or are we using single sign-on? */ + if(userp && *userp) { + /* Populate our identity structure */ + result = Curl_create_sspi_identity(userp, passwdp, &krb5->identity); + if(result) + return result; + + /* Allow proper cleanup of the identity structure */ + krb5->p_identity = &krb5->identity; + } + else + /* Use the current Windows user */ + krb5->p_identity = NULL; + + /* Allocate our credentials handle */ + krb5->credentials = calloc(1, sizeof(CredHandle)); + if(!krb5->credentials) + return CURLE_OUT_OF_MEMORY; + + /* Acquire our credentials handle */ + status = s_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *) + TEXT(SP_NAME_KERBEROS), + SECPKG_CRED_OUTBOUND, NULL, + krb5->p_identity, NULL, NULL, + krb5->credentials, &expiry); + if(status != SEC_E_OK) + return CURLE_LOGIN_DENIED; + + /* Allocate our new context handle */ + krb5->context = calloc(1, sizeof(CtxtHandle)); + if(!krb5->context) + return CURLE_OUT_OF_MEMORY; + } + + if(chlg) { + if(!Curl_bufref_len(chlg)) { + infof(data, "GSSAPI handshake failure (empty challenge message)"); + return CURLE_BAD_CONTENT_ENCODING; + } + + /* Setup the challenge "input" security buffer */ + chlg_desc.ulVersion = SECBUFFER_VERSION; + chlg_desc.cBuffers = 1; + chlg_desc.pBuffers = &chlg_buf; + chlg_buf.BufferType = SECBUFFER_TOKEN; + chlg_buf.pvBuffer = (void *) Curl_bufref_ptr(chlg); + chlg_buf.cbBuffer = curlx_uztoul(Curl_bufref_len(chlg)); + } + + /* Setup the response "output" security buffer */ + resp_desc.ulVersion = SECBUFFER_VERSION; + resp_desc.cBuffers = 1; + resp_desc.pBuffers = &resp_buf; + resp_buf.BufferType = SECBUFFER_TOKEN; + resp_buf.pvBuffer = krb5->output_token; + resp_buf.cbBuffer = curlx_uztoul(krb5->token_max); + + /* Generate our challenge-response message */ + status = s_pSecFn->InitializeSecurityContext(krb5->credentials, + chlg ? krb5->context : NULL, + krb5->spn, + (mutual_auth ? + ISC_REQ_MUTUAL_AUTH : 0), + 0, SECURITY_NATIVE_DREP, + chlg ? &chlg_desc : NULL, 0, + &context, + &resp_desc, &attrs, + &expiry); + + if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + + if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) + return CURLE_AUTH_ERROR; + + if(memcmp(&context, krb5->context, sizeof(context))) { + s_pSecFn->DeleteSecurityContext(krb5->context); + + memcpy(krb5->context, &context, sizeof(context)); + } + + if(resp_buf.cbBuffer) { + result = Curl_bufref_memdup(out, resp_buf.pvBuffer, resp_buf.cbBuffer); + } + else if(mutual_auth) + Curl_bufref_set(out, "", 0, NULL); + else + Curl_bufref_set(out, NULL, 0, NULL); + + return result; +} + +/* + * Curl_auth_create_gssapi_security_message() + * + * This is used to generate an already encoded GSSAPI (Kerberos V5) security + * token message ready for sending to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * authzid [in] - The authorization identity if some. + * chlg [in] - The optional challenge message. + * krb5 [in/out] - The Kerberos 5 data struct being used and modified. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, + const char *authzid, + const struct bufref *chlg, + struct kerberos5data *krb5, + struct bufref *out) +{ + size_t offset = 0; + size_t messagelen = 0; + size_t appdatalen = 0; + unsigned char *trailer = NULL; + unsigned char *message = NULL; + unsigned char *padding = NULL; + unsigned char *appdata = NULL; + SecBuffer input_buf[2]; + SecBuffer wrap_buf[3]; + SecBufferDesc input_desc; + SecBufferDesc wrap_desc; + unsigned char *indata; + unsigned long qop = 0; + unsigned long sec_layer = 0; + unsigned long max_size = 0; + SecPkgContext_Sizes sizes; + SECURITY_STATUS status; + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) data; +#endif + + /* Ensure we have a valid challenge message */ + if(!Curl_bufref_len(chlg)) { + infof(data, "GSSAPI handshake failure (empty security message)"); + return CURLE_BAD_CONTENT_ENCODING; + } + + /* Get our response size information */ + status = s_pSecFn->QueryContextAttributes(krb5->context, + SECPKG_ATTR_SIZES, + &sizes); + + if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + + if(status != SEC_E_OK) + return CURLE_AUTH_ERROR; + + /* Setup the "input" security buffer */ + input_desc.ulVersion = SECBUFFER_VERSION; + input_desc.cBuffers = 2; + input_desc.pBuffers = input_buf; + input_buf[0].BufferType = SECBUFFER_STREAM; + input_buf[0].pvBuffer = (void *) Curl_bufref_ptr(chlg); + input_buf[0].cbBuffer = curlx_uztoul(Curl_bufref_len(chlg)); + input_buf[1].BufferType = SECBUFFER_DATA; + input_buf[1].pvBuffer = NULL; + input_buf[1].cbBuffer = 0; + + /* Decrypt the inbound challenge and obtain the qop */ + status = s_pSecFn->DecryptMessage(krb5->context, &input_desc, 0, &qop); + if(status != SEC_E_OK) { + infof(data, "GSSAPI handshake failure (empty security message)"); + return CURLE_BAD_CONTENT_ENCODING; + } + + /* Not 4 octets long so fail as per RFC4752 Section 3.1 */ + if(input_buf[1].cbBuffer != 4) { + infof(data, "GSSAPI handshake failure (invalid security data)"); + return CURLE_BAD_CONTENT_ENCODING; + } + + /* Extract the security layer and the maximum message size */ + indata = input_buf[1].pvBuffer; + sec_layer = indata[0]; + max_size = (indata[1] << 16) | (indata[2] << 8) | indata[3]; + + /* Free the challenge as it is not required anymore */ + s_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer); + + /* Process the security layer */ + if(!(sec_layer & KERB_WRAP_NO_ENCRYPT)) { + infof(data, "GSSAPI handshake failure (invalid security layer)"); + return CURLE_BAD_CONTENT_ENCODING; + } + sec_layer &= KERB_WRAP_NO_ENCRYPT; /* We do not support a security layer */ + + /* Process the maximum message size the server can receive */ + if(max_size > 0) { + /* The server has told us it supports a maximum receive buffer, however, as + we don't require one unless we are encrypting data, we tell the server + our receive buffer is zero. */ + max_size = 0; + } + + /* Allocate the trailer */ + trailer = malloc(sizes.cbSecurityTrailer); + if(!trailer) + return CURLE_OUT_OF_MEMORY; + + /* Allocate our message */ + messagelen = 4; + if(authzid) + messagelen += strlen(authzid); + message = malloc(messagelen); + if(!message) { + free(trailer); + + return CURLE_OUT_OF_MEMORY; + } + + /* Populate the message with the security layer and client supported receive + message size. */ + message[0] = sec_layer & 0xFF; + message[1] = (max_size >> 16) & 0xFF; + message[2] = (max_size >> 8) & 0xFF; + message[3] = max_size & 0xFF; + + /* If given, append the authorization identity. */ + + if(authzid && *authzid) + memcpy(message + 4, authzid, messagelen - 4); + + /* Allocate the padding */ + padding = malloc(sizes.cbBlockSize); + if(!padding) { + free(message); + free(trailer); + + return CURLE_OUT_OF_MEMORY; + } + + /* Setup the "authentication data" security buffer */ + wrap_desc.ulVersion = SECBUFFER_VERSION; + wrap_desc.cBuffers = 3; + wrap_desc.pBuffers = wrap_buf; + wrap_buf[0].BufferType = SECBUFFER_TOKEN; + wrap_buf[0].pvBuffer = trailer; + wrap_buf[0].cbBuffer = sizes.cbSecurityTrailer; + wrap_buf[1].BufferType = SECBUFFER_DATA; + wrap_buf[1].pvBuffer = message; + wrap_buf[1].cbBuffer = curlx_uztoul(messagelen); + wrap_buf[2].BufferType = SECBUFFER_PADDING; + wrap_buf[2].pvBuffer = padding; + wrap_buf[2].cbBuffer = sizes.cbBlockSize; + + /* Encrypt the data */ + status = s_pSecFn->EncryptMessage(krb5->context, KERB_WRAP_NO_ENCRYPT, + &wrap_desc, 0); + if(status != SEC_E_OK) { + free(padding); + free(message); + free(trailer); + + if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + + return CURLE_AUTH_ERROR; + } + + /* Allocate the encryption (wrap) buffer */ + appdatalen = wrap_buf[0].cbBuffer + wrap_buf[1].cbBuffer + + wrap_buf[2].cbBuffer; + appdata = malloc(appdatalen); + if(!appdata) { + free(padding); + free(message); + free(trailer); + + return CURLE_OUT_OF_MEMORY; + } + + /* Populate the encryption buffer */ + memcpy(appdata, wrap_buf[0].pvBuffer, wrap_buf[0].cbBuffer); + offset += wrap_buf[0].cbBuffer; + memcpy(appdata + offset, wrap_buf[1].pvBuffer, wrap_buf[1].cbBuffer); + offset += wrap_buf[1].cbBuffer; + memcpy(appdata + offset, wrap_buf[2].pvBuffer, wrap_buf[2].cbBuffer); + + /* Free all of our local buffers */ + free(padding); + free(message); + free(trailer); + + /* Return the response. */ + Curl_bufref_set(out, appdata, appdatalen, curl_free); + return CURLE_OK; +} + +/* + * Curl_auth_cleanup_gssapi() + * + * This is used to clean up the GSSAPI (Kerberos V5) specific data. + * + * Parameters: + * + * krb5 [in/out] - The Kerberos 5 data struct being cleaned up. + * + */ +void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5) +{ + /* Free our security context */ + if(krb5->context) { + s_pSecFn->DeleteSecurityContext(krb5->context); + free(krb5->context); + krb5->context = NULL; + } + + /* Free our credentials handle */ + if(krb5->credentials) { + s_pSecFn->FreeCredentialsHandle(krb5->credentials); + free(krb5->credentials); + krb5->credentials = NULL; + } + + /* Free our identity */ + Curl_sspi_free_identity(krb5->p_identity); + krb5->p_identity = NULL; + + /* Free the SPN and output token */ + Curl_safefree(krb5->spn); + Curl_safefree(krb5->output_token); + + /* Reset any variables */ + krb5->token_max = 0; +} + +#endif /* USE_WINDOWS_SSPI && USE_KERBEROS5 */ diff --git a/Utilities/cmcurl/lib/vauth/ntlm.c b/Utilities/cmcurl/lib/vauth/ntlm.c new file mode 100644 index 0000000..ed7cee8 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/ntlm.c @@ -0,0 +1,780 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_NTLM) && !defined(USE_WINDOWS_SSPI) + +/* + * NTLM details: + * + * https://davenport.sourceforge.net/ntlm.html + * https://www.innovation.ch/java/ntlm.html + */ + +#define DEBUG_ME 0 + +#include "urldata.h" +#include "sendf.h" +#include "curl_ntlm_core.h" +#include "curl_gethostname.h" +#include "curl_multibyte.h" +#include "curl_md5.h" +#include "warnless.h" +#include "rand.h" +#include "vtls/vtls.h" + +#define BUILDING_CURL_NTLM_MSGS_C +#include "vauth/vauth.h" +#include "vauth/ntlm.h" +#include "curl_endian.h" +#include "curl_printf.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* "NTLMSSP" signature is always in ASCII regardless of the platform */ +#define NTLMSSP_SIGNATURE "\x4e\x54\x4c\x4d\x53\x53\x50" + +/* The fixed host name we provide, in order to not leak our real local host + name. Copy the name used by Firefox. */ +#define NTLM_HOSTNAME "WORKSTATION" + +#if DEBUG_ME +# define DEBUG_OUT(x) x +static void ntlm_print_flags(FILE *handle, unsigned long flags) +{ + if(flags & NTLMFLAG_NEGOTIATE_UNICODE) + fprintf(handle, "NTLMFLAG_NEGOTIATE_UNICODE "); + if(flags & NTLMFLAG_NEGOTIATE_OEM) + fprintf(handle, "NTLMFLAG_NEGOTIATE_OEM "); + if(flags & NTLMFLAG_REQUEST_TARGET) + fprintf(handle, "NTLMFLAG_REQUEST_TARGET "); + if(flags & (1<<3)) + fprintf(handle, "NTLMFLAG_UNKNOWN_3 "); + if(flags & NTLMFLAG_NEGOTIATE_SIGN) + fprintf(handle, "NTLMFLAG_NEGOTIATE_SIGN "); + if(flags & NTLMFLAG_NEGOTIATE_SEAL) + fprintf(handle, "NTLMFLAG_NEGOTIATE_SEAL "); + if(flags & NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE) + fprintf(handle, "NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE "); + if(flags & NTLMFLAG_NEGOTIATE_LM_KEY) + fprintf(handle, "NTLMFLAG_NEGOTIATE_LM_KEY "); + if(flags & NTLMFLAG_NEGOTIATE_NTLM_KEY) + fprintf(handle, "NTLMFLAG_NEGOTIATE_NTLM_KEY "); + if(flags & (1<<10)) + fprintf(handle, "NTLMFLAG_UNKNOWN_10 "); + if(flags & NTLMFLAG_NEGOTIATE_ANONYMOUS) + fprintf(handle, "NTLMFLAG_NEGOTIATE_ANONYMOUS "); + if(flags & NTLMFLAG_NEGOTIATE_DOMAIN_SUPPLIED) + fprintf(handle, "NTLMFLAG_NEGOTIATE_DOMAIN_SUPPLIED "); + if(flags & NTLMFLAG_NEGOTIATE_WORKSTATION_SUPPLIED) + fprintf(handle, "NTLMFLAG_NEGOTIATE_WORKSTATION_SUPPLIED "); + if(flags & NTLMFLAG_NEGOTIATE_LOCAL_CALL) + fprintf(handle, "NTLMFLAG_NEGOTIATE_LOCAL_CALL "); + if(flags & NTLMFLAG_NEGOTIATE_ALWAYS_SIGN) + fprintf(handle, "NTLMFLAG_NEGOTIATE_ALWAYS_SIGN "); + if(flags & NTLMFLAG_TARGET_TYPE_DOMAIN) + fprintf(handle, "NTLMFLAG_TARGET_TYPE_DOMAIN "); + if(flags & NTLMFLAG_TARGET_TYPE_SERVER) + fprintf(handle, "NTLMFLAG_TARGET_TYPE_SERVER "); + if(flags & NTLMFLAG_TARGET_TYPE_SHARE) + fprintf(handle, "NTLMFLAG_TARGET_TYPE_SHARE "); + if(flags & NTLMFLAG_NEGOTIATE_NTLM2_KEY) + fprintf(handle, "NTLMFLAG_NEGOTIATE_NTLM2_KEY "); + if(flags & NTLMFLAG_REQUEST_INIT_RESPONSE) + fprintf(handle, "NTLMFLAG_REQUEST_INIT_RESPONSE "); + if(flags & NTLMFLAG_REQUEST_ACCEPT_RESPONSE) + fprintf(handle, "NTLMFLAG_REQUEST_ACCEPT_RESPONSE "); + if(flags & NTLMFLAG_REQUEST_NONNT_SESSION_KEY) + fprintf(handle, "NTLMFLAG_REQUEST_NONNT_SESSION_KEY "); + if(flags & NTLMFLAG_NEGOTIATE_TARGET_INFO) + fprintf(handle, "NTLMFLAG_NEGOTIATE_TARGET_INFO "); + if(flags & (1<<24)) + fprintf(handle, "NTLMFLAG_UNKNOWN_24 "); + if(flags & (1<<25)) + fprintf(handle, "NTLMFLAG_UNKNOWN_25 "); + if(flags & (1<<26)) + fprintf(handle, "NTLMFLAG_UNKNOWN_26 "); + if(flags & (1<<27)) + fprintf(handle, "NTLMFLAG_UNKNOWN_27 "); + if(flags & (1<<28)) + fprintf(handle, "NTLMFLAG_UNKNOWN_28 "); + if(flags & NTLMFLAG_NEGOTIATE_128) + fprintf(handle, "NTLMFLAG_NEGOTIATE_128 "); + if(flags & NTLMFLAG_NEGOTIATE_KEY_EXCHANGE) + fprintf(handle, "NTLMFLAG_NEGOTIATE_KEY_EXCHANGE "); + if(flags & NTLMFLAG_NEGOTIATE_56) + fprintf(handle, "NTLMFLAG_NEGOTIATE_56 "); +} + +static void ntlm_print_hex(FILE *handle, const char *buf, size_t len) +{ + const char *p = buf; + + (void) handle; + + fprintf(stderr, "0x"); + while(len-- > 0) + fprintf(stderr, "%02.2x", (unsigned int)*p++); +} +#else +# define DEBUG_OUT(x) Curl_nop_stmt +#endif + +/* + * ntlm_decode_type2_target() + * + * This is used to decode the "target info" in the NTLM type-2 message + * received. + * + * Parameters: + * + * data [in] - The session handle. + * type2ref [in] - The type-2 message. + * ntlm [in/out] - The NTLM data struct being used and modified. + * + * Returns CURLE_OK on success. + */ +static CURLcode ntlm_decode_type2_target(struct Curl_easy *data, + const struct bufref *type2ref, + struct ntlmdata *ntlm) +{ + unsigned short target_info_len = 0; + unsigned int target_info_offset = 0; + const unsigned char *type2 = Curl_bufref_ptr(type2ref); + size_t type2len = Curl_bufref_len(type2ref); + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) data; +#endif + + if(type2len >= 48) { + target_info_len = Curl_read16_le(&type2[40]); + target_info_offset = Curl_read32_le(&type2[44]); + if(target_info_len > 0) { + if((target_info_offset > type2len) || + (target_info_offset + target_info_len) > type2len || + target_info_offset < 48) { + infof(data, "NTLM handshake failure (bad type-2 message). " + "Target Info Offset Len is set incorrect by the peer"); + return CURLE_BAD_CONTENT_ENCODING; + } + + free(ntlm->target_info); /* replace any previous data */ + ntlm->target_info = malloc(target_info_len); + if(!ntlm->target_info) + return CURLE_OUT_OF_MEMORY; + + memcpy(ntlm->target_info, &type2[target_info_offset], target_info_len); + } + } + + ntlm->target_info_len = target_info_len; + + return CURLE_OK; +} + +/* + NTLM message structure notes: + + A 'short' is a 'network short', a little-endian 16-bit unsigned value. + + A 'long' is a 'network long', a little-endian, 32-bit unsigned value. + + A 'security buffer' represents a triplet used to point to a buffer, + consisting of two shorts and one long: + + 1. A 'short' containing the length of the buffer content in bytes. + 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() + * + * This is used to evaluate if NTLM is supported. + * + * Parameters: None + * + * Returns TRUE as NTLM as handled by libcurl. + */ +bool Curl_auth_is_ntlm_supported(void) +{ + return TRUE; +} + +/* + * Curl_auth_decode_ntlm_type2_message() + * + * This is used to decode an NTLM type-2 message. The raw NTLM message is + * checked * for validity before the appropriate data for creating a type-3 + * message is * written to the given NTLM data structure. + * + * Parameters: + * + * data [in] - The session handle. + * type2ref [in] - The type-2 message. + * ntlm [in/out] - The NTLM data struct being used and modified. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, + const struct bufref *type2ref, + struct ntlmdata *ntlm) +{ + static const char type2_marker[] = { 0x02, 0x00, 0x00, 0x00 }; + + /* NTLM type-2 message structure: + + Index Description Content + 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + (0x4e544c4d53535000) + 8 NTLM Message Type long (0x02000000) + 12 Target Name security buffer + 20 Flags long + 24 Challenge 8 bytes + (32) Context 8 bytes (two consecutive longs) (*) + (40) Target Information security buffer (*) + (48) OS Version Structure 8 bytes (*) + 32 (48) (56) Start of data block (*) + (*) -> Optional + */ + + CURLcode result = CURLE_OK; + const unsigned char *type2 = Curl_bufref_ptr(type2ref); + size_t type2len = Curl_bufref_len(type2ref); + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void)data; +#endif + + ntlm->flags = 0; + + if((type2len < 32) || + (memcmp(type2, NTLMSSP_SIGNATURE, 8) != 0) || + (memcmp(type2 + 8, type2_marker, sizeof(type2_marker)) != 0)) { + /* This was not a good enough type-2 message */ + infof(data, "NTLM handshake failure (bad type-2 message)"); + return CURLE_BAD_CONTENT_ENCODING; + } + + ntlm->flags = Curl_read32_le(&type2[20]); + memcpy(ntlm->nonce, &type2[24], 8); + + if(ntlm->flags & NTLMFLAG_NEGOTIATE_TARGET_INFO) { + result = ntlm_decode_type2_target(data, type2ref, ntlm); + if(result) { + infof(data, "NTLM handshake failure (bad type-2 message)"); + return result; + } + } + + DEBUG_OUT({ + fprintf(stderr, "**** TYPE2 header flags=0x%08.8lx ", ntlm->flags); + ntlm_print_flags(stderr, ntlm->flags); + fprintf(stderr, "\n nonce="); + ntlm_print_hex(stderr, (char *)ntlm->nonce, 8); + fprintf(stderr, "\n****\n"); + fprintf(stderr, "**** Header %s\n ", header); + }); + + return result; +} + +/* copy the source to the destination and fill in zeroes in every + other destination byte! */ +static void unicodecpy(unsigned char *dest, const char *src, size_t length) +{ + size_t i; + for(i = 0; i < length; i++) { + dest[2 * i] = (unsigned char)src[i]; + dest[2 * i + 1] = '\0'; + } +} + +/* + * Curl_auth_create_ntlm_type1_message() + * + * This is used to generate an NTLM type-1 message ready for sending to the + * recipient using the appropriate compile time crypto API. + * + * Parameters: + * + * data [in] - The session handle. + * userp [in] - The user name in the format User or Domain\User. + * passwdp [in] - The user's password. + * service [in] - The service type such as http, smtp, pop or imap. + * host [in] - The host name. + * ntlm [in/out] - The NTLM data struct being used and modified. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + const char *service, + const char *hostname, + struct ntlmdata *ntlm, + struct bufref *out) +{ + /* NTLM type-1 message structure: + + Index Description Content + 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + (0x4e544c4d53535000) + 8 NTLM Message Type long (0x01000000) + 12 Flags long + (16) Supplied Domain security buffer (*) + (24) Supplied Workstation security buffer (*) + (32) OS Version Structure 8 bytes (*) + (32) (40) Start of data block (*) + (*) -> Optional + */ + + size_t size; + + char *ntlmbuf; + const char *host = ""; /* empty */ + const char *domain = ""; /* empty */ + size_t hostlen = 0; + size_t domlen = 0; + size_t hostoff = 0; + size_t domoff = hostoff + hostlen; /* This is 0: remember that host and + domain are empty */ + (void)data; + (void)userp; + (void)passwdp; + (void)service; + (void)hostname; + + /* Clean up any former leftovers and initialise to defaults */ + Curl_auth_cleanup_ntlm(ntlm); + + ntlmbuf = aprintf(NTLMSSP_SIGNATURE "%c" + "\x01%c%c%c" /* 32-bit type = 1 */ + "%c%c%c%c" /* 32-bit NTLM flag field */ + "%c%c" /* domain length */ + "%c%c" /* domain allocated space */ + "%c%c" /* domain name offset */ + "%c%c" /* 2 zeroes */ + "%c%c" /* host length */ + "%c%c" /* host allocated space */ + "%c%c" /* host name offset */ + "%c%c" /* 2 zeroes */ + "%s" /* host name */ + "%s", /* domain string */ + 0, /* trailing zero */ + 0, 0, 0, /* part of type-1 long */ + + LONGQUARTET(NTLMFLAG_NEGOTIATE_OEM | + NTLMFLAG_REQUEST_TARGET | + NTLMFLAG_NEGOTIATE_NTLM_KEY | + NTLMFLAG_NEGOTIATE_NTLM2_KEY | + NTLMFLAG_NEGOTIATE_ALWAYS_SIGN), + SHORTPAIR(domlen), + SHORTPAIR(domlen), + SHORTPAIR(domoff), + 0, 0, + SHORTPAIR(hostlen), + SHORTPAIR(hostlen), + SHORTPAIR(hostoff), + 0, 0, + host, /* this is empty */ + domain /* this is empty */); + + if(!ntlmbuf) + return CURLE_OUT_OF_MEMORY; + + /* Initial packet length */ + size = 32 + hostlen + domlen; + + DEBUG_OUT({ + fprintf(stderr, "* TYPE1 header flags=0x%02.2x%02.2x%02.2x%02.2x " + "0x%08.8x ", + LONGQUARTET(NTLMFLAG_NEGOTIATE_OEM | + NTLMFLAG_REQUEST_TARGET | + NTLMFLAG_NEGOTIATE_NTLM_KEY | + NTLMFLAG_NEGOTIATE_NTLM2_KEY | + NTLMFLAG_NEGOTIATE_ALWAYS_SIGN), + NTLMFLAG_NEGOTIATE_OEM | + NTLMFLAG_REQUEST_TARGET | + NTLMFLAG_NEGOTIATE_NTLM_KEY | + NTLMFLAG_NEGOTIATE_NTLM2_KEY | + NTLMFLAG_NEGOTIATE_ALWAYS_SIGN); + ntlm_print_flags(stderr, + NTLMFLAG_NEGOTIATE_OEM | + NTLMFLAG_REQUEST_TARGET | + NTLMFLAG_NEGOTIATE_NTLM_KEY | + NTLMFLAG_NEGOTIATE_NTLM2_KEY | + NTLMFLAG_NEGOTIATE_ALWAYS_SIGN); + fprintf(stderr, "\n****\n"); + }); + + Curl_bufref_set(out, ntlmbuf, size, curl_free); + return CURLE_OK; +} + +/* + * Curl_auth_create_ntlm_type3_message() + * + * This is used to generate an already encoded NTLM type-3 message ready for + * sending to the recipient using the appropriate compile time crypto API. + * + * Parameters: + * + * data [in] - The session handle. + * userp [in] - The user name in the format User or Domain\User. + * passwdp [in] - The user's password. + * ntlm [in/out] - The NTLM data struct being used and modified. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + struct ntlmdata *ntlm, + struct bufref *out) +{ + /* NTLM type-3 message structure: + + Index Description Content + 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 User Name security buffer + 44 Workstation Name security buffer + (52) Session Key security buffer (*) + (60) Flags long (*) + (64) OS Version Structure 8 bytes (*) + 52 (64) (72) Start of data block + (*) -> Optional + */ + + CURLcode result = CURLE_OK; + size_t size; + unsigned char ntlmbuf[NTLM_BUFSIZE]; + int lmrespoff; + unsigned char lmresp[24]; /* fixed-size */ + int ntrespoff; + unsigned int ntresplen = 24; + unsigned char ntresp[24]; /* fixed-size */ + unsigned char *ptr_ntresp = &ntresp[0]; + unsigned char *ntlmv2resp = NULL; + bool unicode = (ntlm->flags & NTLMFLAG_NEGOTIATE_UNICODE) ? TRUE : FALSE; + char host[HOSTNAME_MAX + 1] = ""; + const char *user; + const char *domain = ""; + size_t hostoff = 0; + size_t useroff = 0; + size_t domoff = 0; + size_t hostlen = 0; + size_t userlen = 0; + size_t domlen = 0; + + memset(lmresp, 0, sizeof(lmresp)); + memset(ntresp, 0, sizeof(ntresp)); + user = strchr(userp, '\\'); + if(!user) + user = strchr(userp, '/'); + + if(user) { + domain = userp; + domlen = (user - domain); + user++; + } + else + user = userp; + + userlen = strlen(user); + +#ifndef NTLM_HOSTNAME + /* Get the machine's un-qualified host name as NTLM doesn't like the fully + qualified domain name */ + if(Curl_gethostname(host, sizeof(host))) { + infof(data, "gethostname() failed, continuing without"); + hostlen = 0; + } + else { + hostlen = strlen(host); + } +#else + (void)msnprintf(host, sizeof(host), "%s", NTLM_HOSTNAME); + hostlen = sizeof(NTLM_HOSTNAME)-1; +#endif + + if(ntlm->flags & NTLMFLAG_NEGOTIATE_NTLM2_KEY) { + unsigned char ntbuffer[0x18]; + unsigned char entropy[8]; + unsigned char ntlmv2hash[0x18]; + + /* Full NTLM version 2 + Although this cannot be negotiated, it is used here if available, as + servers featuring extended security are likely supporting also + NTLMv2. */ + result = Curl_rand(data, entropy, 8); + if(result) + return result; + + result = Curl_ntlm_core_mk_nt_hash(passwdp, ntbuffer); + if(result) + return result; + + result = Curl_ntlm_core_mk_ntlmv2_hash(user, userlen, domain, domlen, + ntbuffer, ntlmv2hash); + if(result) + return result; + + /* LMv2 response */ + result = Curl_ntlm_core_mk_lmv2_resp(ntlmv2hash, entropy, + &ntlm->nonce[0], lmresp); + if(result) + return result; + + /* NTLMv2 response */ + result = Curl_ntlm_core_mk_ntlmv2_resp(ntlmv2hash, entropy, + ntlm, &ntlmv2resp, &ntresplen); + if(result) + return result; + + ptr_ntresp = ntlmv2resp; + } + else { + + unsigned char ntbuffer[0x18]; + unsigned char lmbuffer[0x18]; + + /* NTLM version 1 */ + + result = Curl_ntlm_core_mk_nt_hash(passwdp, ntbuffer); + if(result) + return result; + + Curl_ntlm_core_lm_resp(ntbuffer, &ntlm->nonce[0], ntresp); + + result = Curl_ntlm_core_mk_lm_hash(passwdp, lmbuffer); + if(result) + return result; + + Curl_ntlm_core_lm_resp(lmbuffer, &ntlm->nonce[0], lmresp); + ntlm->flags &= ~NTLMFLAG_NEGOTIATE_NTLM2_KEY; + + /* A safer but less compatible alternative is: + * Curl_ntlm_core_lm_resp(ntbuffer, &ntlm->nonce[0], lmresp); + * See https://davenport.sourceforge.net/ntlm.html#ntlmVersion2 */ + } + + if(unicode) { + domlen = domlen * 2; + userlen = userlen * 2; + hostlen = hostlen * 2; + } + + lmrespoff = 64; /* size of the message header */ + ntrespoff = lmrespoff + 0x18; + domoff = ntrespoff + ntresplen; + useroff = domoff + domlen; + hostoff = useroff + userlen; + + /* Create the big type-3 message binary blob */ + size = msnprintf((char *)ntlmbuf, NTLM_BUFSIZE, + NTLMSSP_SIGNATURE "%c" + "\x03%c%c%c" /* 32-bit type = 3 */ + + "%c%c" /* LanManager length */ + "%c%c" /* LanManager allocated space */ + "%c%c" /* LanManager offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* NT-response length */ + "%c%c" /* NT-response allocated space */ + "%c%c" /* NT-response offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* domain length */ + "%c%c" /* domain allocated space */ + "%c%c" /* domain name offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* user length */ + "%c%c" /* user allocated space */ + "%c%c" /* user offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* host length */ + "%c%c" /* host allocated space */ + "%c%c" /* host offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* session key length (unknown purpose) */ + "%c%c" /* session key allocated space (unknown purpose) */ + "%c%c" /* session key offset (unknown purpose) */ + "%c%c" /* 2 zeroes */ + + "%c%c%c%c", /* flags */ + + /* domain string */ + /* user string */ + /* host string */ + /* LanManager response */ + /* NT response */ + + 0, /* null-termination */ + 0, 0, 0, /* type-3 long, the 24 upper bits */ + + SHORTPAIR(0x18), /* LanManager response length, twice */ + SHORTPAIR(0x18), + SHORTPAIR(lmrespoff), + 0x0, 0x0, + + SHORTPAIR(ntresplen), /* NT-response length, twice */ + SHORTPAIR(ntresplen), + SHORTPAIR(ntrespoff), + 0x0, 0x0, + + SHORTPAIR(domlen), + SHORTPAIR(domlen), + SHORTPAIR(domoff), + 0x0, 0x0, + + SHORTPAIR(userlen), + SHORTPAIR(userlen), + SHORTPAIR(useroff), + 0x0, 0x0, + + SHORTPAIR(hostlen), + SHORTPAIR(hostlen), + SHORTPAIR(hostoff), + 0x0, 0x0, + + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + + LONGQUARTET(ntlm->flags)); + + DEBUGASSERT(size == 64); + DEBUGASSERT(size == (size_t)lmrespoff); + + /* We append the binary hashes */ + if(size < (NTLM_BUFSIZE - 0x18)) { + memcpy(&ntlmbuf[size], lmresp, 0x18); + size += 0x18; + } + + DEBUG_OUT({ + fprintf(stderr, "**** TYPE3 header lmresp="); + ntlm_print_hex(stderr, (char *)&ntlmbuf[lmrespoff], 0x18); + }); + + /* ntresplen + size should not be risking an integer overflow here */ + if(ntresplen + size > sizeof(ntlmbuf)) { + failf(data, "incoming NTLM message too big"); + return CURLE_OUT_OF_MEMORY; + } + DEBUGASSERT(size == (size_t)ntrespoff); + memcpy(&ntlmbuf[size], ptr_ntresp, ntresplen); + size += ntresplen; + + DEBUG_OUT({ + fprintf(stderr, "\n ntresp="); + ntlm_print_hex(stderr, (char *)&ntlmbuf[ntrespoff], ntresplen); + }); + + free(ntlmv2resp);/* Free the dynamic buffer allocated for NTLMv2 */ + + DEBUG_OUT({ + fprintf(stderr, "\n flags=0x%02.2x%02.2x%02.2x%02.2x 0x%08.8x ", + LONGQUARTET(ntlm->flags), ntlm->flags); + ntlm_print_flags(stderr, ntlm->flags); + fprintf(stderr, "\n****\n"); + }); + + /* Make sure that the domain, user and host strings fit in the + buffer before we copy them there. */ + if(size + userlen + domlen + hostlen >= NTLM_BUFSIZE) { + failf(data, "user + domain + host name too big"); + return CURLE_OUT_OF_MEMORY; + } + + DEBUGASSERT(size == domoff); + if(unicode) + unicodecpy(&ntlmbuf[size], domain, domlen / 2); + else + memcpy(&ntlmbuf[size], domain, domlen); + + size += domlen; + + DEBUGASSERT(size == useroff); + if(unicode) + unicodecpy(&ntlmbuf[size], user, userlen / 2); + else + memcpy(&ntlmbuf[size], user, userlen); + + size += userlen; + + DEBUGASSERT(size == hostoff); + if(unicode) + unicodecpy(&ntlmbuf[size], host, hostlen / 2); + else + memcpy(&ntlmbuf[size], host, hostlen); + + size += hostlen; + + /* Return the binary blob. */ + result = Curl_bufref_memdup(out, ntlmbuf, size); + + Curl_auth_cleanup_ntlm(ntlm); + + return result; +} + +/* + * Curl_auth_cleanup_ntlm() + * + * This is used to clean up the NTLM specific data. + * + * Parameters: + * + * ntlm [in/out] - The NTLM data struct being cleaned up. + * + */ +void Curl_auth_cleanup_ntlm(struct ntlmdata *ntlm) +{ + /* Free the target info */ + Curl_safefree(ntlm->target_info); + + /* Reset any variables */ + ntlm->target_info_len = 0; +} + +#endif /* USE_NTLM && !USE_WINDOWS_SSPI */ diff --git a/Utilities/cmcurl/lib/vauth/ntlm.h b/Utilities/cmcurl/lib/vauth/ntlm.h new file mode 100644 index 0000000..31ce921 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/ntlm.h @@ -0,0 +1,143 @@ +#ifndef HEADER_VAUTH_NTLM_H +#define HEADER_VAUTH_NTLM_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_NTLM + +/* NTLM buffer fixed size, large enough for long user + host + domain */ +#define NTLM_BUFSIZE 1024 + +/* Stuff only required for curl_ntlm_msgs.c */ +#ifdef BUILDING_CURL_NTLM_MSGS_C + +/* Flag bits definitions based on + https://davenport.sourceforge.net/ntlm.html */ + +#define NTLMFLAG_NEGOTIATE_UNICODE (1<<0) +/* Indicates that Unicode strings are supported for use in security buffer + data. */ + +#define NTLMFLAG_NEGOTIATE_OEM (1<<1) +/* Indicates that OEM strings are supported for use in security buffer data. */ + +#define NTLMFLAG_REQUEST_TARGET (1<<2) +/* Requests that the server's authentication realm be included in the Type 2 + message. */ + +/* unknown (1<<3) */ +#define NTLMFLAG_NEGOTIATE_SIGN (1<<4) +/* Specifies that authenticated communication between the client and server + should carry a digital signature (message integrity). */ + +#define NTLMFLAG_NEGOTIATE_SEAL (1<<5) +/* Specifies that authenticated communication between the client and server + should be encrypted (message confidentiality). */ + +#define NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE (1<<6) +/* Indicates that datagram authentication is being used. */ + +#define NTLMFLAG_NEGOTIATE_LM_KEY (1<<7) +/* Indicates that the LAN Manager session key should be used for signing and + sealing authenticated communications. */ + +#define NTLMFLAG_NEGOTIATE_NTLM_KEY (1<<9) +/* Indicates that NTLM authentication is being used. */ + +/* unknown (1<<10) */ + +#define NTLMFLAG_NEGOTIATE_ANONYMOUS (1<<11) +/* Sent by the client in the Type 3 message to indicate that an anonymous + context has been established. This also affects the response fields. */ + +#define NTLMFLAG_NEGOTIATE_DOMAIN_SUPPLIED (1<<12) +/* Sent by the client in the Type 1 message to indicate that a desired + authentication realm is included in the message. */ + +#define NTLMFLAG_NEGOTIATE_WORKSTATION_SUPPLIED (1<<13) +/* Sent by the client in the Type 1 message to indicate that the client + workstation's name is included in the message. */ + +#define NTLMFLAG_NEGOTIATE_LOCAL_CALL (1<<14) +/* Sent by the server to indicate that the server and client are on the same + machine. Implies that the client may use a pre-established local security + context rather than responding to the challenge. */ + +#define NTLMFLAG_NEGOTIATE_ALWAYS_SIGN (1<<15) +/* Indicates that authenticated communication between the client and server + should be signed with a "dummy" signature. */ + +#define NTLMFLAG_TARGET_TYPE_DOMAIN (1<<16) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a domain. */ + +#define NTLMFLAG_TARGET_TYPE_SERVER (1<<17) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a server. */ + +#define NTLMFLAG_TARGET_TYPE_SHARE (1<<18) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a share. Presumably, this is for share-level + authentication. Usage is unclear. */ + +#define NTLMFLAG_NEGOTIATE_NTLM2_KEY (1<<19) +/* Indicates that the NTLM2 signing and sealing scheme should be used for + protecting authenticated communications. */ + +#define NTLMFLAG_REQUEST_INIT_RESPONSE (1<<20) +/* unknown purpose */ + +#define NTLMFLAG_REQUEST_ACCEPT_RESPONSE (1<<21) +/* unknown purpose */ + +#define NTLMFLAG_REQUEST_NONNT_SESSION_KEY (1<<22) +/* unknown purpose */ + +#define NTLMFLAG_NEGOTIATE_TARGET_INFO (1<<23) +/* Sent by the server in the Type 2 message to indicate that it is including a + Target Information block in the message. */ + +/* unknown (1<24) */ +/* unknown (1<25) */ +/* unknown (1<26) */ +/* unknown (1<27) */ +/* unknown (1<28) */ + +#define NTLMFLAG_NEGOTIATE_128 (1<<29) +/* Indicates that 128-bit encryption is supported. */ + +#define NTLMFLAG_NEGOTIATE_KEY_EXCHANGE (1<<30) +/* Indicates that the client will provide an encrypted master key in + the "Session Key" field of the Type 3 message. */ + +#define NTLMFLAG_NEGOTIATE_56 (1<<31) +/* Indicates that 56-bit encryption is supported. */ + +#endif /* BUILDING_CURL_NTLM_MSGS_C */ + +#endif /* USE_NTLM */ + +#endif /* HEADER_VAUTH_NTLM_H */ diff --git a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c new file mode 100644 index 0000000..5118963 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c @@ -0,0 +1,372 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_WINDOWS_SSPI) && defined(USE_NTLM) + +#include <curl/curl.h> + +#include "vauth/vauth.h" +#include "urldata.h" +#include "curl_ntlm_core.h" +#include "warnless.h" +#include "curl_multibyte.h" +#include "sendf.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_auth_is_ntlm_supported() + * + * This is used to evaluate if NTLM is supported. + * + * Parameters: None + * + * Returns TRUE if NTLM is supported by Windows SSPI. + */ +bool Curl_auth_is_ntlm_supported(void) +{ + PSecPkgInfo SecurityPackage; + SECURITY_STATUS status; + + /* Query the security package for NTLM */ + status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), + &SecurityPackage); + + /* Release the package buffer as it is not required anymore */ + if(status == SEC_E_OK) { + s_pSecFn->FreeContextBuffer(SecurityPackage); + } + + return (status == SEC_E_OK ? TRUE : FALSE); +} + +/* + * Curl_auth_create_ntlm_type1_message() + * + * This is used to generate an already encoded NTLM type-1 message ready for + * sending to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * userp [in] - The user name in the format User or Domain\User. + * passwdp [in] - The user's password. + * service [in] - The service type such as http, smtp, pop or imap. + * host [in] - The host name. + * ntlm [in/out] - The NTLM data struct being used and modified. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + const char *service, + const char *host, + struct ntlmdata *ntlm, + struct bufref *out) +{ + PSecPkgInfo SecurityPackage; + SecBuffer type_1_buf; + SecBufferDesc type_1_desc; + SECURITY_STATUS status; + unsigned long attrs; + TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ + + /* Clean up any former leftovers and initialise to defaults */ + Curl_auth_cleanup_ntlm(ntlm); + + /* Query the security package for NTLM */ + status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), + &SecurityPackage); + if(status != SEC_E_OK) { + failf(data, "SSPI: couldn't get auth info"); + return CURLE_AUTH_ERROR; + } + + ntlm->token_max = SecurityPackage->cbMaxToken; + + /* Release the package buffer as it is not required anymore */ + s_pSecFn->FreeContextBuffer(SecurityPackage); + + /* Allocate our output buffer */ + ntlm->output_token = malloc(ntlm->token_max); + if(!ntlm->output_token) + return CURLE_OUT_OF_MEMORY; + + if(userp && *userp) { + CURLcode result; + + /* Populate our identity structure */ + result = Curl_create_sspi_identity(userp, passwdp, &ntlm->identity); + if(result) + return result; + + /* Allow proper cleanup of the identity structure */ + ntlm->p_identity = &ntlm->identity; + } + else + /* Use the current Windows user */ + ntlm->p_identity = NULL; + + /* Allocate our credentials handle */ + ntlm->credentials = calloc(1, sizeof(CredHandle)); + if(!ntlm->credentials) + return CURLE_OUT_OF_MEMORY; + + /* Acquire our credentials handle */ + status = s_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *) TEXT(SP_NAME_NTLM), + SECPKG_CRED_OUTBOUND, NULL, + ntlm->p_identity, NULL, NULL, + ntlm->credentials, &expiry); + if(status != SEC_E_OK) + return CURLE_LOGIN_DENIED; + + /* Allocate our new context handle */ + ntlm->context = calloc(1, sizeof(CtxtHandle)); + if(!ntlm->context) + return CURLE_OUT_OF_MEMORY; + + ntlm->spn = Curl_auth_build_spn(service, host, NULL); + if(!ntlm->spn) + return CURLE_OUT_OF_MEMORY; + + /* Setup the type-1 "output" security buffer */ + type_1_desc.ulVersion = SECBUFFER_VERSION; + type_1_desc.cBuffers = 1; + type_1_desc.pBuffers = &type_1_buf; + type_1_buf.BufferType = SECBUFFER_TOKEN; + type_1_buf.pvBuffer = ntlm->output_token; + type_1_buf.cbBuffer = curlx_uztoul(ntlm->token_max); + + /* Generate our type-1 message */ + status = s_pSecFn->InitializeSecurityContext(ntlm->credentials, NULL, + ntlm->spn, + 0, 0, SECURITY_NETWORK_DREP, + NULL, 0, + ntlm->context, &type_1_desc, + &attrs, &expiry); + if(status == SEC_I_COMPLETE_NEEDED || + status == SEC_I_COMPLETE_AND_CONTINUE) + s_pSecFn->CompleteAuthToken(ntlm->context, &type_1_desc); + else if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) + return CURLE_AUTH_ERROR; + + /* Return the response. */ + Curl_bufref_set(out, ntlm->output_token, type_1_buf.cbBuffer, NULL); + return CURLE_OK; +} + +/* + * Curl_auth_decode_ntlm_type2_message() + * + * This is used to decode an already encoded NTLM type-2 message. + * + * Parameters: + * + * data [in] - The session handle. + * type2 [in] - The type-2 message. + * ntlm [in/out] - The NTLM data struct being used and modified. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, + const struct bufref *type2, + struct ntlmdata *ntlm) +{ +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) data; +#endif + + /* Ensure we have a valid type-2 message */ + if(!Curl_bufref_len(type2)) { + infof(data, "NTLM handshake failure (empty type-2 message)"); + return CURLE_BAD_CONTENT_ENCODING; + } + + /* Store the challenge for later use */ + ntlm->input_token = malloc(Curl_bufref_len(type2) + 1); + if(!ntlm->input_token) + return CURLE_OUT_OF_MEMORY; + memcpy(ntlm->input_token, Curl_bufref_ptr(type2), Curl_bufref_len(type2)); + ntlm->input_token[Curl_bufref_len(type2)] = '\0'; + ntlm->input_token_len = Curl_bufref_len(type2); + + return CURLE_OK; +} + +/* +* Curl_auth_create_ntlm_type3_message() + * Curl_auth_create_ntlm_type3_message() + * + * This is used to generate an already encoded NTLM type-3 message ready for + * sending to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * userp [in] - The user name in the format User or Domain\User. + * passwdp [in] - The user's password. + * ntlm [in/out] - The NTLM data struct being used and modified. + * out [out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + struct ntlmdata *ntlm, + struct bufref *out) +{ + CURLcode result = CURLE_OK; + SecBuffer type_2_bufs[2]; + SecBuffer type_3_buf; + SecBufferDesc type_2_desc; + SecBufferDesc type_3_desc; + SECURITY_STATUS status; + unsigned long attrs; + TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) data; +#endif + (void) passwdp; + (void) userp; + + /* Setup the type-2 "input" security buffer */ + type_2_desc.ulVersion = SECBUFFER_VERSION; + type_2_desc.cBuffers = 1; + type_2_desc.pBuffers = &type_2_bufs[0]; + type_2_bufs[0].BufferType = SECBUFFER_TOKEN; + 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 + * otherwise IIS will not pass the authentication (401 response). + * Minimum supported version is Windows 7. + * https://docs.microsoft.com/en-us/security-updates + * /SecurityAdvisories/2009/973811 + */ + if(ntlm->sslContext) { + SEC_CHANNEL_BINDINGS channelBindings; + SecPkgContext_Bindings pkgBindings; + pkgBindings.Bindings = &channelBindings; + status = s_pSecFn->QueryContextAttributes( + ntlm->sslContext, + SECPKG_ATTR_ENDPOINT_BINDINGS, + &pkgBindings + ); + if(status == SEC_E_OK) { + type_2_desc.cBuffers++; + type_2_bufs[1].BufferType = SECBUFFER_CHANNEL_BINDINGS; + type_2_bufs[1].cbBuffer = pkgBindings.BindingsLength; + type_2_bufs[1].pvBuffer = pkgBindings.Bindings; + } + } +#endif + + /* Setup the type-3 "output" security buffer */ + type_3_desc.ulVersion = SECBUFFER_VERSION; + type_3_desc.cBuffers = 1; + type_3_desc.pBuffers = &type_3_buf; + type_3_buf.BufferType = SECBUFFER_TOKEN; + type_3_buf.pvBuffer = ntlm->output_token; + type_3_buf.cbBuffer = curlx_uztoul(ntlm->token_max); + + /* Generate our type-3 message */ + status = s_pSecFn->InitializeSecurityContext(ntlm->credentials, + ntlm->context, + ntlm->spn, + 0, 0, SECURITY_NETWORK_DREP, + &type_2_desc, + 0, ntlm->context, + &type_3_desc, + &attrs, &expiry); + if(status != SEC_E_OK) { + infof(data, "NTLM handshake failure (type-3 message): Status=%x", + status); + + if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + + return CURLE_AUTH_ERROR; + } + + /* Return the response. */ + result = Curl_bufref_memdup(out, ntlm->output_token, type_3_buf.cbBuffer); + Curl_auth_cleanup_ntlm(ntlm); + return result; +} + +/* + * Curl_auth_cleanup_ntlm() + * + * This is used to clean up the NTLM specific data. + * + * Parameters: + * + * ntlm [in/out] - The NTLM data struct being cleaned up. + * + */ +void Curl_auth_cleanup_ntlm(struct ntlmdata *ntlm) +{ + /* Free our security context */ + if(ntlm->context) { + s_pSecFn->DeleteSecurityContext(ntlm->context); + free(ntlm->context); + ntlm->context = NULL; + } + + /* Free our credentials handle */ + if(ntlm->credentials) { + s_pSecFn->FreeCredentialsHandle(ntlm->credentials); + free(ntlm->credentials); + ntlm->credentials = NULL; + } + + /* Free our identity */ + Curl_sspi_free_identity(ntlm->p_identity); + ntlm->p_identity = NULL; + + /* Free the input and output tokens */ + Curl_safefree(ntlm->input_token); + Curl_safefree(ntlm->output_token); + + /* Reset any variables */ + ntlm->token_max = 0; + + Curl_safefree(ntlm->spn); +} + +#endif /* USE_WINDOWS_SSPI && USE_NTLM */ diff --git a/Utilities/cmcurl/lib/vauth/oauth2.c b/Utilities/cmcurl/lib/vauth/oauth2.c new file mode 100644 index 0000000..a4adbdc --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/oauth2.c @@ -0,0 +1,108 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC6749 OAuth 2.0 Authorization Framework + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_POP3) || \ + (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)) + +#include <curl/curl.h> +#include "urldata.h" + +#include "vauth/vauth.h" +#include "warnless.h" +#include "curl_printf.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_auth_create_oauth_bearer_message() + * + * This is used to generate an OAuth 2.0 message ready for sending to the + * recipient. + * + * Parameters: + * + * user[in] - The user name. + * host[in] - The host name. + * port[in] - The port(when not Port 80). + * bearer[in] - The bearer token. + * out[out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_oauth_bearer_message(const char *user, + const char *host, + const long port, + const char *bearer, + struct bufref *out) +{ + char *oauth; + + /* Generate the message */ + if(port == 0 || port == 80) + oauth = aprintf("n,a=%s,\1host=%s\1auth=Bearer %s\1\1", user, host, + bearer); + else + oauth = aprintf("n,a=%s,\1host=%s\1port=%ld\1auth=Bearer %s\1\1", user, + host, port, bearer); + if(!oauth) + return CURLE_OUT_OF_MEMORY; + + Curl_bufref_set(out, oauth, strlen(oauth), curl_free); + return CURLE_OK; +} + +/* + * Curl_auth_create_xoauth_bearer_message() + * + * This is used to generate a XOAuth 2.0 message ready for * sending to the + * recipient. + * + * Parameters: + * + * user[in] - The user name. + * bearer[in] - The bearer token. + * out[out] - The result storage. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_xoauth_bearer_message(const char *user, + const char *bearer, + struct bufref *out) +{ + /* Generate the message */ + char *xoauth = aprintf("user=%s\1auth=Bearer %s\1\1", user, bearer); + if(!xoauth) + return CURLE_OUT_OF_MEMORY; + + Curl_bufref_set(out, xoauth, strlen(xoauth), curl_free); + return CURLE_OK; +} +#endif /* disabled, no users */ diff --git a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c new file mode 100644 index 0000000..e1d52b7 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c @@ -0,0 +1,281 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC4178 Simple and Protected GSS-API Negotiation Mechanism + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(HAVE_GSSAPI) && defined(USE_SPNEGO) + +#include <curl/curl.h> + +#include "vauth/vauth.h" +#include "urldata.h" +#include "curl_base64.h" +#include "curl_gssapi.h" +#include "warnless.h" +#include "curl_multibyte.h" +#include "sendf.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_auth_is_spnego_supported() + * + * This is used to evaluate if SPNEGO (Negotiate) is supported. + * + * Parameters: None + * + * Returns TRUE if Negotiate supported by the GSS-API library. + */ +bool Curl_auth_is_spnego_supported(void) +{ + return TRUE; +} + +/* + * Curl_auth_decode_spnego_message() + * + * This is used to decode an already encoded SPNEGO (Negotiate) challenge + * message. + * + * Parameters: + * + * data [in] - The session handle. + * userp [in] - The user name in the format User or Domain\User. + * passwdp [in] - The user's password. + * service [in] - The service type such as http, smtp, pop or imap. + * host [in] - The host name. + * chlg64 [in] - The optional base64 encoded challenge message. + * nego [in/out] - The Negotiate data struct being used and modified. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, + const char *user, + const char *password, + const char *service, + const char *host, + const char *chlg64, + struct negotiatedata *nego) +{ + CURLcode result = CURLE_OK; + size_t chlglen = 0; + unsigned char *chlg = NULL; + OM_uint32 major_status; + OM_uint32 minor_status; + OM_uint32 unused_status; + gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + + (void) user; + (void) password; + + if(nego->context && nego->status == GSS_S_COMPLETE) { + /* We finished successfully our part of authentication, but server + * rejected it (since we're again here). Exit with an error since we + * can't invent anything better */ + Curl_auth_cleanup_spnego(nego); + return CURLE_LOGIN_DENIED; + } + + if(!nego->spn) { + /* Generate our SPN */ + char *spn = Curl_auth_build_spn(service, NULL, host); + if(!spn) + return CURLE_OUT_OF_MEMORY; + + /* Populate the SPN structure */ + spn_token.value = spn; + spn_token.length = strlen(spn); + + /* Import the SPN */ + major_status = gss_import_name(&minor_status, &spn_token, + GSS_C_NT_HOSTBASED_SERVICE, + &nego->spn); + if(GSS_ERROR(major_status)) { + Curl_gss_log_error(data, "gss_import_name() failed: ", + major_status, minor_status); + + free(spn); + + return CURLE_AUTH_ERROR; + } + + free(spn); + } + + if(chlg64 && *chlg64) { + /* Decode the base-64 encoded challenge message */ + if(*chlg64 != '=') { + result = Curl_base64_decode(chlg64, &chlg, &chlglen); + if(result) + return result; + } + + /* Ensure we have a valid challenge message */ + if(!chlg) { + infof(data, "SPNEGO handshake failure (empty challenge message)"); + return CURLE_BAD_CONTENT_ENCODING; + } + + /* Setup the challenge "input" security buffer */ + input_token.value = chlg; + input_token.length = chlglen; + } + + /* Generate our challenge-response message */ + major_status = Curl_gss_init_sec_context(data, + &minor_status, + &nego->context, + nego->spn, + &Curl_spnego_mech_oid, + GSS_C_NO_CHANNEL_BINDINGS, + &input_token, + &output_token, + TRUE, + NULL); + + /* Free the decoded challenge as it is not required anymore */ + Curl_safefree(input_token.value); + + nego->status = major_status; + if(GSS_ERROR(major_status)) { + if(output_token.value) + gss_release_buffer(&unused_status, &output_token); + + Curl_gss_log_error(data, "gss_init_sec_context() failed: ", + major_status, minor_status); + + return CURLE_AUTH_ERROR; + } + + if(!output_token.value || !output_token.length) { + if(output_token.value) + gss_release_buffer(&unused_status, &output_token); + + return CURLE_AUTH_ERROR; + } + + /* Free previous token */ + if(nego->output_token.length && nego->output_token.value) + gss_release_buffer(&unused_status, &nego->output_token); + + nego->output_token = output_token; + + return CURLE_OK; +} + +/* + * Curl_auth_create_spnego_message() + * + * This is used to generate an already encoded SPNEGO (Negotiate) response + * message ready for sending to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * nego [in/out] - The Negotiate data struct being used and modified. + * outptr [in/out] - The address where a pointer to newly allocated memory + * holding the result will be stored upon completion. + * outlen [out] - The length of the output message. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_spnego_message(struct negotiatedata *nego, + char **outptr, size_t *outlen) +{ + CURLcode result; + OM_uint32 minor_status; + + /* Base64 encode the already generated response */ + result = Curl_base64_encode(nego->output_token.value, + nego->output_token.length, + outptr, outlen); + + if(result) { + gss_release_buffer(&minor_status, &nego->output_token); + nego->output_token.value = NULL; + nego->output_token.length = 0; + + return result; + } + + if(!*outptr || !*outlen) { + gss_release_buffer(&minor_status, &nego->output_token); + nego->output_token.value = NULL; + nego->output_token.length = 0; + + return CURLE_REMOTE_ACCESS_DENIED; + } + + return CURLE_OK; +} + +/* + * Curl_auth_cleanup_spnego() + * + * This is used to clean up the SPNEGO (Negotiate) specific data. + * + * Parameters: + * + * nego [in/out] - The Negotiate data struct being cleaned up. + * + */ +void Curl_auth_cleanup_spnego(struct negotiatedata *nego) +{ + OM_uint32 minor_status; + + /* Free our security context */ + if(nego->context != GSS_C_NO_CONTEXT) { + gss_delete_sec_context(&minor_status, &nego->context, GSS_C_NO_BUFFER); + nego->context = GSS_C_NO_CONTEXT; + } + + /* Free the output token */ + if(nego->output_token.value) { + gss_release_buffer(&minor_status, &nego->output_token); + nego->output_token.value = NULL; + nego->output_token.length = 0; + + } + + /* Free the SPN */ + if(nego->spn != GSS_C_NO_NAME) { + gss_release_name(&minor_status, &nego->spn); + nego->spn = GSS_C_NO_NAME; + } + + /* Reset any variables */ + nego->status = 0; + nego->noauthpersist = FALSE; + nego->havenoauthpersist = FALSE; + nego->havenegdata = FALSE; + nego->havemultiplerequests = FALSE; +} + +#endif /* HAVE_GSSAPI && USE_SPNEGO */ diff --git a/Utilities/cmcurl/lib/vauth/spnego_sspi.c b/Utilities/cmcurl/lib/vauth/spnego_sspi.c new file mode 100644 index 0000000..d3245d0 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/spnego_sspi.c @@ -0,0 +1,364 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + * RFC4178 Simple and Protected GSS-API Negotiation Mechanism + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_WINDOWS_SSPI) && defined(USE_SPNEGO) + +#include <curl/curl.h> + +#include "vauth/vauth.h" +#include "urldata.h" +#include "curl_base64.h" +#include "warnless.h" +#include "curl_multibyte.h" +#include "sendf.h" +#include "strerror.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_auth_is_spnego_supported() + * + * This is used to evaluate if SPNEGO (Negotiate) is supported. + * + * Parameters: None + * + * Returns TRUE if Negotiate is supported by Windows SSPI. + */ +bool Curl_auth_is_spnego_supported(void) +{ + PSecPkgInfo SecurityPackage; + SECURITY_STATUS status; + + /* Query the security package for Negotiate */ + status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) + TEXT(SP_NAME_NEGOTIATE), + &SecurityPackage); + + /* Release the package buffer as it is not required anymore */ + if(status == SEC_E_OK) { + s_pSecFn->FreeContextBuffer(SecurityPackage); + } + + + return (status == SEC_E_OK ? TRUE : FALSE); +} + +/* + * Curl_auth_decode_spnego_message() + * + * This is used to decode an already encoded SPNEGO (Negotiate) challenge + * message. + * + * Parameters: + * + * data [in] - The session handle. + * user [in] - The user name in the format User or Domain\User. + * password [in] - The user's password. + * service [in] - The service type such as http, smtp, pop or imap. + * host [in] - The host name. + * chlg64 [in] - The optional base64 encoded challenge message. + * nego [in/out] - The Negotiate data struct being used and modified. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, + const char *user, + const char *password, + const char *service, + const char *host, + const char *chlg64, + struct negotiatedata *nego) +{ + CURLcode result = CURLE_OK; + size_t chlglen = 0; + unsigned char *chlg = NULL; + PSecPkgInfo SecurityPackage; + SecBuffer chlg_buf[2]; + SecBuffer resp_buf; + SecBufferDesc chlg_desc; + SecBufferDesc resp_desc; + unsigned long attrs; + TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) data; +#endif + + if(nego->context && nego->status == SEC_E_OK) { + /* We finished successfully our part of authentication, but server + * rejected it (since we're again here). Exit with an error since we + * can't invent anything better */ + Curl_auth_cleanup_spnego(nego); + return CURLE_LOGIN_DENIED; + } + + if(!nego->spn) { + /* Generate our SPN */ + nego->spn = Curl_auth_build_spn(service, host, NULL); + if(!nego->spn) + return CURLE_OUT_OF_MEMORY; + } + + if(!nego->output_token) { + /* Query the security package for Negotiate */ + nego->status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) + TEXT(SP_NAME_NEGOTIATE), + &SecurityPackage); + if(nego->status != SEC_E_OK) { + failf(data, "SSPI: couldn't get auth info"); + return CURLE_AUTH_ERROR; + } + + nego->token_max = SecurityPackage->cbMaxToken; + + /* Release the package buffer as it is not required anymore */ + s_pSecFn->FreeContextBuffer(SecurityPackage); + + /* Allocate our output buffer */ + nego->output_token = malloc(nego->token_max); + if(!nego->output_token) + return CURLE_OUT_OF_MEMORY; + } + + if(!nego->credentials) { + /* Do we have credentials to use or are we using single sign-on? */ + if(user && *user) { + /* Populate our identity structure */ + result = Curl_create_sspi_identity(user, password, &nego->identity); + if(result) + return result; + + /* Allow proper cleanup of the identity structure */ + nego->p_identity = &nego->identity; + } + else + /* Use the current Windows user */ + nego->p_identity = NULL; + + /* Allocate our credentials handle */ + nego->credentials = calloc(1, sizeof(CredHandle)); + if(!nego->credentials) + return CURLE_OUT_OF_MEMORY; + + /* Acquire our credentials handle */ + nego->status = + s_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *)TEXT(SP_NAME_NEGOTIATE), + SECPKG_CRED_OUTBOUND, NULL, + nego->p_identity, NULL, NULL, + nego->credentials, &expiry); + if(nego->status != SEC_E_OK) + return CURLE_AUTH_ERROR; + + /* Allocate our new context handle */ + nego->context = calloc(1, sizeof(CtxtHandle)); + if(!nego->context) + return CURLE_OUT_OF_MEMORY; + } + + if(chlg64 && *chlg64) { + /* Decode the base-64 encoded challenge message */ + if(*chlg64 != '=') { + result = Curl_base64_decode(chlg64, &chlg, &chlglen); + if(result) + return result; + } + + /* Ensure we have a valid challenge message */ + if(!chlg) { + infof(data, "SPNEGO handshake failure (empty challenge message)"); + return CURLE_BAD_CONTENT_ENCODING; + } + + /* 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 will not pass the authentication (401 response). + * Minimum supported version is Windows 7. + * https://docs.microsoft.com/en-us/security-updates + * /SecurityAdvisories/2009/973811 + */ + if(nego->sslContext) { + SEC_CHANNEL_BINDINGS channelBindings; + SecPkgContext_Bindings pkgBindings; + pkgBindings.Bindings = &channelBindings; + nego->status = s_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; + } + } +#endif + } + + /* Setup the response "output" security buffer */ + resp_desc.ulVersion = SECBUFFER_VERSION; + resp_desc.cBuffers = 1; + resp_desc.pBuffers = &resp_buf; + resp_buf.BufferType = SECBUFFER_TOKEN; + resp_buf.pvBuffer = nego->output_token; + resp_buf.cbBuffer = curlx_uztoul(nego->token_max); + + /* Generate our challenge-response message */ + nego->status = s_pSecFn->InitializeSecurityContext(nego->credentials, + chlg ? nego->context : + NULL, + nego->spn, + ISC_REQ_CONFIDENTIALITY, + 0, SECURITY_NATIVE_DREP, + chlg ? &chlg_desc : NULL, + 0, nego->context, + &resp_desc, &attrs, + &expiry); + + /* Free the decoded challenge as it is not required anymore */ + free(chlg); + + if(GSS_ERROR(nego->status)) { + char buffer[STRERROR_LEN]; + failf(data, "InitializeSecurityContext failed: %s", + Curl_sspi_strerror(nego->status, buffer, sizeof(buffer))); + + if(nego->status == (DWORD)SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + + return CURLE_AUTH_ERROR; + } + + if(nego->status == SEC_I_COMPLETE_NEEDED || + nego->status == SEC_I_COMPLETE_AND_CONTINUE) { + nego->status = s_pSecFn->CompleteAuthToken(nego->context, &resp_desc); + if(GSS_ERROR(nego->status)) { + char buffer[STRERROR_LEN]; + failf(data, "CompleteAuthToken failed: %s", + Curl_sspi_strerror(nego->status, buffer, sizeof(buffer))); + + if(nego->status == (DWORD)SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + + return CURLE_AUTH_ERROR; + } + } + + nego->output_token_length = resp_buf.cbBuffer; + + return result; +} + +/* + * Curl_auth_create_spnego_message() + * + * This is used to generate an already encoded SPNEGO (Negotiate) response + * message ready for sending to the recipient. + * + * Parameters: + * + * data [in] - The session handle. + * nego [in/out] - The Negotiate data struct being used and modified. + * outptr [in/out] - The address where a pointer to newly allocated memory + * holding the result will be stored upon completion. + * outlen [out] - The length of the output message. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_auth_create_spnego_message(struct negotiatedata *nego, + char **outptr, size_t *outlen) +{ + /* Base64 encode the already generated response */ + CURLcode result = Curl_base64_encode((const char *) nego->output_token, + nego->output_token_length, outptr, + outlen); + if(!result && (!*outptr || !*outlen)) { + free(*outptr); + result = CURLE_REMOTE_ACCESS_DENIED; + } + + return result; +} + +/* + * Curl_auth_cleanup_spnego() + * + * This is used to clean up the SPNEGO (Negotiate) specific data. + * + * Parameters: + * + * nego [in/out] - The Negotiate data struct being cleaned up. + * + */ +void Curl_auth_cleanup_spnego(struct negotiatedata *nego) +{ + /* Free our security context */ + if(nego->context) { + s_pSecFn->DeleteSecurityContext(nego->context); + free(nego->context); + nego->context = NULL; + } + + /* Free our credentials handle */ + if(nego->credentials) { + s_pSecFn->FreeCredentialsHandle(nego->credentials); + free(nego->credentials); + nego->credentials = NULL; + } + + /* Free our identity */ + Curl_sspi_free_identity(nego->p_identity); + nego->p_identity = NULL; + + /* Free the SPN and output token */ + Curl_safefree(nego->spn); + Curl_safefree(nego->output_token); + + /* Reset any variables */ + nego->status = 0; + nego->token_max = 0; + nego->noauthpersist = FALSE; + nego->havenoauthpersist = FALSE; + nego->havenegdata = FALSE; + nego->havemultiplerequests = FALSE; +} + +#endif /* USE_WINDOWS_SSPI && USE_SPNEGO */ diff --git a/Utilities/cmcurl/lib/vauth/vauth.c b/Utilities/cmcurl/lib/vauth/vauth.c new file mode 100644 index 0000000..62fc7c4 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/vauth.c @@ -0,0 +1,163 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * + * 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 <curl/curl.h> + +#include "vauth.h" +#include "urldata.h" +#include "strcase.h" +#include "curl_multibyte.h" +#include "curl_printf.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_auth_build_spn() + * + * This is used to build a SPN string in the following formats: + * + * service/host@realm (Not currently used) + * service/host (Not used by GSS-API) + * service@realm (Not used by Windows SSPI) + * + * Parameters: + * + * service [in] - The service type such as http, smtp, pop or imap. + * host [in] - The host name. + * realm [in] - The realm. + * + * Returns a pointer to the newly allocated SPN. + */ +#if !defined(USE_WINDOWS_SSPI) +char *Curl_auth_build_spn(const char *service, const char *host, + const char *realm) +{ + char *spn = NULL; + + /* Generate our SPN */ + if(host && realm) + spn = aprintf("%s/%s@%s", service, host, realm); + else if(host) + spn = aprintf("%s/%s", service, host); + else if(realm) + spn = aprintf("%s@%s", service, realm); + + /* Return our newly allocated SPN */ + return spn; +} +#else +TCHAR *Curl_auth_build_spn(const char *service, const char *host, + const char *realm) +{ + char *utf8_spn = NULL; + TCHAR *tchar_spn = NULL; + TCHAR *dupe_tchar_spn = NULL; + + (void) realm; + + /* Note: We could use DsMakeSPN() or DsClientMakeSpnForTargetServer() rather + than doing this ourselves but the first is only available in Windows XP + and Windows Server 2003 and the latter is only available in Windows 2000 + but not Windows95/98/ME or Windows NT4.0 unless the Active Directory + Client Extensions are installed. As such it is far simpler for us to + formulate the SPN instead. */ + + /* Generate our UTF8 based SPN */ + utf8_spn = aprintf("%s/%s", service, host); + if(!utf8_spn) + return NULL; + + /* Allocate and return a TCHAR based SPN. Since curlx_convert_UTF8_to_tchar + must be freed by curlx_unicodefree we'll dupe the result so that the + pointer this function returns can be normally free'd. */ + tchar_spn = curlx_convert_UTF8_to_tchar(utf8_spn); + free(utf8_spn); + if(!tchar_spn) + return NULL; + dupe_tchar_spn = _tcsdup(tchar_spn); + curlx_unicodefree(tchar_spn); + return dupe_tchar_spn; +} +#endif /* USE_WINDOWS_SSPI */ + +/* + * Curl_auth_user_contains_domain() + * + * This is used to test if the specified user contains a Windows domain name as + * follows: + * + * Domain\User (Down-level Logon Name) + * Domain/User (curl Down-level format - for compatibility with existing code) + * User@Domain (User Principal Name) + * + * Note: The user name may be empty when using a GSS-API library or Windows + * SSPI as the user and domain are either obtained from the credentials cache + * when using GSS-API or via the currently logged in user's credentials when + * using Windows SSPI. + * + * Parameters: + * + * user [in] - The user name. + * + * Returns TRUE on success; otherwise FALSE. + */ +bool Curl_auth_user_contains_domain(const char *user) +{ + bool valid = FALSE; + + if(user && *user) { + /* Check we have a domain name or UPN present */ + char *p = strpbrk(user, "\\/@"); + + valid = (p != NULL && p > user && p < user + strlen(user) - 1 ? TRUE : + FALSE); + } +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + else + /* User and domain are obtained from the GSS-API credentials cache or the + currently logged in user from Windows */ + valid = TRUE; +#endif + + return valid; +} + +/* + * Curl_auth_ollowed_to_host() tells if authentication, cookies or other + * "sensitive data" can (still) be sent to this host. + */ +bool Curl_auth_allowed_to_host(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + return (!data->state.this_is_a_follow || + data->set.allow_auth_to_other_hosts || + (data->state.first_host && + strcasecompare(data->state.first_host, conn->host.name) && + (data->state.first_remote_port == conn->remote_port) && + (data->state.first_remote_protocol == conn->handler->protocol))); +} diff --git a/Utilities/cmcurl/lib/vauth/vauth.h b/Utilities/cmcurl/lib/vauth/vauth.h new file mode 100644 index 0000000..9da0540 --- /dev/null +++ b/Utilities/cmcurl/lib/vauth/vauth.h @@ -0,0 +1,238 @@ +#ifndef HEADER_CURL_VAUTH_H +#define HEADER_CURL_VAUTH_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * + * 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/curl.h> + +#include "bufref.h" + +struct Curl_easy; + +#if !defined(CURL_DISABLE_DIGEST_AUTH) +struct digestdata; +#endif + +#if defined(USE_NTLM) +struct ntlmdata; +#endif + +#if defined(USE_KERBEROS5) +struct kerberos5data; +#endif + +#if (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)) && defined(USE_SPNEGO) +struct negotiatedata; +#endif + +#if defined(USE_GSASL) +struct gsasldata; +#endif + +#if defined(USE_WINDOWS_SSPI) +#define GSS_ERROR(status) ((status) & 0x80000000) +#endif + +/* + * Curl_auth_allowed_to_host() tells if authentication, cookies or other + * "sensitive data" can (still) be sent to this host. + */ +bool Curl_auth_allowed_to_host(struct Curl_easy *data); + +/* This is used to build a SPN string */ +#if !defined(USE_WINDOWS_SSPI) +char *Curl_auth_build_spn(const char *service, const char *host, + const char *realm); +#else +TCHAR *Curl_auth_build_spn(const char *service, const char *host, + const char *realm); +#endif + +/* This is used to test if the user contains a Windows domain name */ +bool Curl_auth_user_contains_domain(const char *user); + +/* This is used to generate a PLAIN cleartext message */ +CURLcode Curl_auth_create_plain_message(const char *authzid, + const char *authcid, + const char *passwd, + struct bufref *out); + +/* This is used to generate a LOGIN cleartext message */ +CURLcode Curl_auth_create_login_message(const char *value, + struct bufref *out); + +/* This is used to generate an EXTERNAL cleartext message */ +CURLcode Curl_auth_create_external_message(const char *user, + struct bufref *out); + +#ifndef CURL_DISABLE_DIGEST_AUTH +/* This is used to generate a CRAM-MD5 response message */ +CURLcode Curl_auth_create_cram_md5_message(const struct bufref *chlg, + const char *userp, + const char *passwdp, + struct bufref *out); + +/* This is used to evaluate if DIGEST is supported */ +bool Curl_auth_is_digest_supported(void); + +/* This is used to generate a base64 encoded DIGEST-MD5 response message */ +CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, + const struct bufref *chlg, + const char *userp, + const char *passwdp, + const char *service, + struct bufref *out); + +/* This is used to decode an HTTP DIGEST challenge message */ +CURLcode Curl_auth_decode_digest_http_message(const char *chlg, + struct digestdata *digest); + +/* This is used to generate an HTTP DIGEST response message */ +CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + const unsigned char *request, + const unsigned char *uri, + struct digestdata *digest, + char **outptr, size_t *outlen); + +/* This is used to clean up the digest specific data */ +void Curl_auth_digest_cleanup(struct digestdata *digest); +#endif /* !CURL_DISABLE_DIGEST_AUTH */ + +#ifdef USE_GSASL +/* This is used to evaluate if MECH is supported by gsasl */ +bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, + const char *mech, + struct gsasldata *gsasl); +/* This is used to start a gsasl method */ +CURLcode Curl_auth_gsasl_start(struct Curl_easy *data, + const char *userp, + const char *passwdp, + struct gsasldata *gsasl); + +/* This is used to process and generate a new SASL token */ +CURLcode Curl_auth_gsasl_token(struct Curl_easy *data, + const struct bufref *chlg, + struct gsasldata *gsasl, + struct bufref *out); + +/* This is used to clean up the gsasl specific data */ +void Curl_auth_gsasl_cleanup(struct gsasldata *digest); +#endif + +#if defined(USE_NTLM) +/* This is used to evaluate if NTLM is supported */ +bool Curl_auth_is_ntlm_supported(void); + +/* This is used to generate a base64 encoded NTLM type-1 message */ +CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + const char *service, + const char *host, + struct ntlmdata *ntlm, + struct bufref *out); + +/* This is used to decode a base64 encoded NTLM type-2 message */ +CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, + const struct bufref *type2, + struct ntlmdata *ntlm); + +/* This is used to generate a base64 encoded NTLM type-3 message */ +CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + struct ntlmdata *ntlm, + struct bufref *out); + +/* This is used to clean up the NTLM specific data */ +void Curl_auth_cleanup_ntlm(struct ntlmdata *ntlm); +#endif /* USE_NTLM */ + +/* This is used to generate a base64 encoded OAuth 2.0 message */ +CURLcode Curl_auth_create_oauth_bearer_message(const char *user, + const char *host, + const long port, + const char *bearer, + struct bufref *out); + +/* This is used to generate a base64 encoded XOAuth 2.0 message */ +CURLcode Curl_auth_create_xoauth_bearer_message(const char *user, + const char *bearer, + struct bufref *out); + +#if defined(USE_KERBEROS5) +/* This is used to evaluate if GSSAPI (Kerberos V5) is supported */ +bool Curl_auth_is_gssapi_supported(void); + +/* This is used to generate a base64 encoded GSSAPI (Kerberos V5) user token + message */ +CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, + const char *userp, + const char *passwdp, + const char *service, + const char *host, + const bool mutual, + const struct bufref *chlg, + struct kerberos5data *krb5, + struct bufref *out); + +/* This is used to generate a base64 encoded GSSAPI (Kerberos V5) security + token message */ +CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, + const char *authzid, + const struct bufref *chlg, + struct kerberos5data *krb5, + struct bufref *out); + +/* This is used to clean up the GSSAPI specific data */ +void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5); +#endif /* USE_KERBEROS5 */ + +#if defined(USE_SPNEGO) +/* This is used to evaluate if SPNEGO (Negotiate) is supported */ +bool Curl_auth_is_spnego_supported(void); + +/* This is used to decode a base64 encoded SPNEGO (Negotiate) challenge + message */ +CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, + const char *user, + const char *password, + const char *service, + const char *host, + const char *chlg64, + struct negotiatedata *nego); + +/* This is used to generate a base64 encoded SPNEGO (Negotiate) response + message */ +CURLcode Curl_auth_create_spnego_message(struct negotiatedata *nego, + char **outptr, size_t *outlen); + +/* This is used to clean up the SPNEGO specific data */ +void Curl_auth_cleanup_spnego(struct negotiatedata *nego); + +#endif /* USE_SPNEGO */ + +#endif /* HEADER_CURL_VAUTH_H */ diff --git a/Utilities/cmcurl/lib/version.c b/Utilities/cmcurl/lib/version.c new file mode 100644 index 0000000..31bd0a4 --- /dev/null +++ b/Utilities/cmcurl/lib/version.c @@ -0,0 +1,674 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_NGHTTP2 +#include <nghttp2/nghttp2.h> +#endif + +#include <curl/curl.h> +#include "urldata.h" +#include "vtls/vtls.h" +#include "http2.h" +#include "vssh/ssh.h" +#include "vquic/vquic.h" +#include "curl_printf.h" +#include "easy_lock.h" + +#ifdef USE_ARES +# if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ + defined(_WIN32) +# define CARES_STATICLIB +# endif +# include <ares.h> +#endif + +#ifdef USE_LIBIDN2 +#include <idn2.h> +#endif + +#ifdef USE_LIBPSL +#include <libpsl.h> +#endif + +#ifdef USE_LIBRTMP +#include <librtmp/rtmp.h> +#endif + +#ifdef HAVE_LIBZ +#include <cm3p/zlib.h> +#endif + +#ifdef HAVE_BROTLI +#if defined(__GNUC__) +/* Ignore -Wvla warnings in brotli headers */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wvla" +#endif +#include <brotli/decode.h> +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#endif + +#ifdef HAVE_ZSTD +#include <zstd.h> +#endif + +#ifdef USE_GSASL +#include <gsasl.h> +#endif + +#ifdef USE_OPENLDAP +#include <ldap.h> +#endif + +#ifdef HAVE_BROTLI +static void brotli_version(char *buf, size_t bufsz) +{ + uint32_t brotli_version = BrotliDecoderVersion(); + unsigned int major = brotli_version >> 24; + unsigned int minor = (brotli_version & 0x00FFFFFF) >> 12; + unsigned int patch = brotli_version & 0x00000FFF; + (void)msnprintf(buf, bufsz, "%u.%u.%u", major, minor, patch); +} +#endif + +#ifdef HAVE_ZSTD +static void zstd_version(char *buf, size_t bufsz) +{ + unsigned long zstd_version = (unsigned long)ZSTD_versionNumber(); + unsigned int major = (unsigned int)(zstd_version / (100 * 100)); + unsigned int minor = (unsigned int)((zstd_version - + (major * 100 * 100)) / 100); + unsigned int patch = (unsigned int)(zstd_version - + (major * 100 * 100) - (minor * 100)); + (void)msnprintf(buf, bufsz, "%u.%u.%u", major, minor, patch); +} +#endif + +/* + * curl_version() returns a pointer to a static buffer. + * + * It is implemented to work multi-threaded by making sure repeated invokes + * generate the exact same string and never write any temporary data like + * zeros in the data. + */ + +#define VERSION_PARTS 16 /* number of substrings we can concatenate */ + +char *curl_version(void) +{ + static char out[300]; + char *outp; + size_t outlen; + const char *src[VERSION_PARTS]; +#ifdef USE_SSL + char ssl_version[200]; +#endif +#ifdef HAVE_LIBZ + char z_version[40]; +#endif +#ifdef HAVE_BROTLI + char br_version[40] = "brotli/"; +#endif +#ifdef HAVE_ZSTD + char zst_version[40] = "zstd/"; +#endif +#ifdef USE_ARES + char cares_version[40]; +#endif +#if defined(USE_LIBIDN2) + char idn_version[40]; +#endif +#ifdef USE_LIBPSL + char psl_version[40]; +#endif +#ifdef USE_SSH + char ssh_version[40]; +#endif +#ifdef USE_NGHTTP2 + char h2_version[40]; +#endif +#ifdef ENABLE_QUIC + char h3_version[40]; +#endif +#ifdef USE_LIBRTMP + char rtmp_version[40]; +#endif +#ifdef USE_HYPER + char hyper_buf[30]; +#endif +#ifdef USE_GSASL + char gsasl_buf[30]; +#endif +#ifdef USE_OPENLDAP + char ldap_buf[30]; +#endif + int i = 0; + int j; + +#ifdef DEBUGBUILD + /* Override version string when environment variable CURL_VERSION is set */ + const char *debugversion = getenv("CURL_VERSION"); + if(debugversion) { + strncpy(out, debugversion, sizeof(out)-1); + out[sizeof(out)-1] = '\0'; + return out; + } +#endif + + src[i++] = LIBCURL_NAME "/" LIBCURL_VERSION; +#ifdef USE_SSL + Curl_ssl_version(ssl_version, sizeof(ssl_version)); + src[i++] = ssl_version; +#endif +#ifdef HAVE_LIBZ + msnprintf(z_version, sizeof(z_version), "zlib/%s", zlibVersion()); + src[i++] = z_version; +#endif +#ifdef HAVE_BROTLI + brotli_version(&br_version[7], sizeof(br_version) - 7); + src[i++] = br_version; +#endif +#ifdef HAVE_ZSTD + zstd_version(&zst_version[5], sizeof(zst_version) - 5); + src[i++] = zst_version; +#endif +#ifdef USE_ARES + msnprintf(cares_version, sizeof(cares_version), + "c-ares/%s", ares_version(NULL)); + src[i++] = cares_version; +#endif +#ifdef USE_LIBIDN2 + msnprintf(idn_version, sizeof(idn_version), + "libidn2/%s", idn2_check_version(NULL)); + src[i++] = idn_version; +#elif defined(USE_WIN32_IDN) + src[i++] = (char *)"WinIDN"; +#endif + +#ifdef USE_LIBPSL + msnprintf(psl_version, sizeof(psl_version), "libpsl/%s", psl_get_version()); + src[i++] = psl_version; +#endif + +#ifdef USE_SSH + Curl_ssh_version(ssh_version, sizeof(ssh_version)); + src[i++] = ssh_version; +#endif +#ifdef USE_NGHTTP2 + Curl_http2_ver(h2_version, sizeof(h2_version)); + src[i++] = h2_version; +#endif +#ifdef ENABLE_QUIC + Curl_quic_ver(h3_version, sizeof(h3_version)); + src[i++] = h3_version; +#endif +#ifdef USE_LIBRTMP + { + char suff[2]; + if(RTMP_LIB_VERSION & 0xff) { + suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; + suff[1] = '\0'; + } + else + suff[0] = '\0'; + + msnprintf(rtmp_version, sizeof(rtmp_version), "librtmp/%d.%d%s", + RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, + suff); + src[i++] = rtmp_version; + } +#endif +#ifdef USE_HYPER + msnprintf(hyper_buf, sizeof(hyper_buf), "Hyper/%s", hyper_version()); + src[i++] = hyper_buf; +#endif +#ifdef USE_GSASL + msnprintf(gsasl_buf, sizeof(gsasl_buf), "libgsasl/%s", + gsasl_check_version(NULL)); + src[i++] = gsasl_buf; +#endif +#ifdef USE_OPENLDAP + { + LDAPAPIInfo api; + api.ldapai_info_version = LDAP_API_INFO_VERSION; + + if(ldap_get_option(NULL, LDAP_OPT_API_INFO, &api) == LDAP_OPT_SUCCESS) { + unsigned int patch = api.ldapai_vendor_version % 100; + unsigned int major = api.ldapai_vendor_version / 10000; + unsigned int minor = + ((api.ldapai_vendor_version - major * 10000) - patch) / 100; + msnprintf(ldap_buf, sizeof(ldap_buf), "%s/%u.%u.%u", + api.ldapai_vendor_name, major, minor, patch); + src[i++] = ldap_buf; + ldap_memfree(api.ldapai_vendor_name); + ber_memvfree((void **)api.ldapai_extensions); + } + } +#endif + + DEBUGASSERT(i <= VERSION_PARTS); + + outp = &out[0]; + outlen = sizeof(out); + for(j = 0; j < i; j++) { + size_t n = strlen(src[j]); + /* we need room for a space, the string and the final zero */ + if(outlen <= (n + 2)) + break; + if(j) { + /* prepend a space if not the first */ + *outp++ = ' '; + outlen--; + } + memcpy(outp, src[j], n); + outp += n; + outlen -= n; + } + *outp = 0; + + return out; +} + +/* data for curl_version_info + + Keep the list sorted alphabetically. It is also written so that each + protocol line has its own #if line to make things easier on the eye. + */ + +static const char * const supported_protocols[] = { +#ifndef CURL_DISABLE_DICT + "dict", +#endif +#ifndef CURL_DISABLE_FILE + "file", +#endif +#ifndef CURL_DISABLE_FTP + "ftp", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) + "ftps", +#endif +#ifndef CURL_DISABLE_GOPHER + "gopher", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_GOPHER) + "gophers", +#endif +#ifndef CURL_DISABLE_HTTP + "http", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) + "https", +#endif +#ifndef CURL_DISABLE_IMAP + "imap", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_IMAP) + "imaps", +#endif +#ifndef CURL_DISABLE_LDAP + "ldap", +#if !defined(CURL_DISABLE_LDAPS) && \ + ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ + (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) + "ldaps", +#endif +#endif +#ifndef CURL_DISABLE_MQTT + "mqtt", +#endif +#ifndef CURL_DISABLE_POP3 + "pop3", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_POP3) + "pop3s", +#endif +#ifdef USE_LIBRTMP + "rtmp", + "rtmpe", + "rtmps", + "rtmpt", + "rtmpte", + "rtmpts", +#endif +#ifndef CURL_DISABLE_RTSP + "rtsp", +#endif +#if defined(USE_SSH) && !defined(USE_WOLFSSH) + "scp", +#endif +#ifdef USE_SSH + "sftp", +#endif +#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) + "smb", +# ifdef USE_SSL + "smbs", +# endif +#endif +#ifndef CURL_DISABLE_SMTP + "smtp", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_SMTP) + "smtps", +#endif +#ifndef CURL_DISABLE_TELNET + "telnet", +#endif +#ifndef CURL_DISABLE_TFTP + "tftp", +#endif +#ifdef USE_WEBSOCKETS + "ws", +#endif +#if defined(USE_SSL) && defined(USE_WEBSOCKETS) + "wss", +#endif + + NULL +}; + +/* + * Feature presence run-time check functions. + * + * Warning: the value returned by these should not change between + * curl_global_init() and curl_global_cleanup() calls. + */ + +#if defined(USE_LIBIDN2) +static int idn_present(curl_version_info_data *info) +{ + return info->libidn != NULL; +} +#else +#define idn_present NULL +#endif + +#if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) && \ + !defined(CURL_DISABLE_HTTP) +static int https_proxy_present(curl_version_info_data *info) +{ + (void) info; + return Curl_ssl_supports(NULL, SSLSUPP_HTTPS_PROXY); +} +#endif + +/* + * Features table. + * + * Keep the features alphabetically sorted. + * Use FEATURE() macro to define an entry: this allows documentation check. + */ + +#define FEATURE(name, present, bitmask) {(name), (present), (bitmask)} + +struct feat { + const char *name; + int (*present)(curl_version_info_data *info); + int bitmask; +}; + +static const struct feat features_table[] = { +#ifndef CURL_DISABLE_ALTSVC + FEATURE("alt-svc", NULL, CURL_VERSION_ALTSVC), +#endif +#ifdef CURLRES_ASYNCH + FEATURE("AsynchDNS", NULL, CURL_VERSION_ASYNCHDNS), +#endif +#ifdef HAVE_BROTLI + FEATURE("brotli", NULL, CURL_VERSION_BROTLI), +#endif +#ifdef DEBUGBUILD + FEATURE("Debug", NULL, CURL_VERSION_DEBUG), +#endif +#ifdef USE_GSASL + FEATURE("gsasl", NULL, CURL_VERSION_GSASL), +#endif +#ifdef HAVE_GSSAPI + FEATURE("GSS-API", NULL, CURL_VERSION_GSSAPI), +#endif +#ifndef CURL_DISABLE_HSTS + FEATURE("HSTS", NULL, CURL_VERSION_HSTS), +#endif +#if defined(USE_NGHTTP2) + FEATURE("HTTP2", NULL, CURL_VERSION_HTTP2), +#endif +#if defined(ENABLE_QUIC) + FEATURE("HTTP3", NULL, CURL_VERSION_HTTP3), +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) && \ + !defined(CURL_DISABLE_HTTP) + FEATURE("HTTPS-proxy", https_proxy_present, CURL_VERSION_HTTPS_PROXY), +#endif +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) + FEATURE("IDN", idn_present, CURL_VERSION_IDN), +#endif +#ifdef ENABLE_IPV6 + FEATURE("IPv6", NULL, CURL_VERSION_IPV6), +#endif +#ifdef USE_KERBEROS5 + FEATURE("Kerberos", NULL, CURL_VERSION_KERBEROS5), +#endif +#if (SIZEOF_CURL_OFF_T > 4) && \ + ( (SIZEOF_OFF_T > 4) || defined(USE_WIN32_LARGE_FILES) ) + FEATURE("Largefile", NULL, CURL_VERSION_LARGEFILE), +#endif +#ifdef HAVE_LIBZ + FEATURE("libz", NULL, CURL_VERSION_LIBZ), +#endif +#ifdef CURL_WITH_MULTI_SSL + FEATURE("MultiSSL", NULL, CURL_VERSION_MULTI_SSL), +#endif +#ifdef USE_NTLM + FEATURE("NTLM", NULL, CURL_VERSION_NTLM), +#endif +#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ + defined(NTLM_WB_ENABLED) + FEATURE("NTLM_WB", NULL, CURL_VERSION_NTLM_WB), +#endif +#if defined(USE_LIBPSL) + FEATURE("PSL", NULL, CURL_VERSION_PSL), +#endif +#ifdef USE_SPNEGO + FEATURE("SPNEGO", NULL, CURL_VERSION_SPNEGO), +#endif +#ifdef USE_SSL + FEATURE("SSL", NULL, CURL_VERSION_SSL), +#endif +#ifdef USE_WINDOWS_SSPI + FEATURE("SSPI", NULL, CURL_VERSION_SSPI), +#endif +#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 +#ifdef CURLDEBUG + FEATURE("TrackMemory", NULL, CURL_VERSION_CURLDEBUG), +#endif +#if defined(_WIN32) && defined(UNICODE) && defined(_UNICODE) + FEATURE("Unicode", NULL, CURL_VERSION_UNICODE), +#endif +#ifdef USE_UNIX_SOCKETS + FEATURE("UnixSockets", NULL, CURL_VERSION_UNIX_SOCKETS), +#endif +#ifdef HAVE_ZSTD + FEATURE("zstd", NULL, CURL_VERSION_ZSTD), +#endif + {NULL, NULL, 0} +}; + +static const char *feature_names[sizeof(features_table) / + sizeof(features_table[0])] = {NULL}; + + +static curl_version_info_data version_info = { + CURLVERSION_NOW, + LIBCURL_VERSION, + LIBCURL_VERSION_NUM, + OS, /* as found by configure or set by hand at build-time */ + 0, /* features bitmask is built at run-time */ + NULL, /* ssl_version */ + 0, /* ssl_version_num, this is kept at zero */ + NULL, /* zlib_version */ + supported_protocols, + NULL, /* c-ares version */ + 0, /* c-ares version numerical */ + NULL, /* libidn version */ + 0, /* iconv version */ + NULL, /* ssh lib version */ + 0, /* brotli_ver_num */ + NULL, /* brotli version */ + 0, /* nghttp2 version number */ + NULL, /* nghttp2 version string */ + NULL, /* quic library string */ +#ifdef CURL_CA_BUNDLE + CURL_CA_BUNDLE, /* cainfo */ +#else + NULL, +#endif +#ifdef CURL_CA_PATH + CURL_CA_PATH, /* capath */ +#else + NULL, +#endif + 0, /* zstd_ver_num */ + NULL, /* zstd version */ + NULL, /* Hyper version */ + NULL, /* gsasl version */ + feature_names +}; + +curl_version_info_data *curl_version_info(CURLversion stamp) +{ + size_t n; + const struct feat *p; + int features = 0; + +#if defined(USE_SSH) + static char ssh_buffer[80]; +#endif +#ifdef USE_SSL +#ifdef CURL_WITH_MULTI_SSL + static char ssl_buffer[200]; +#else + static char ssl_buffer[80]; +#endif +#endif +#ifdef HAVE_BROTLI + static char brotli_buffer[80]; +#endif +#ifdef HAVE_ZSTD + static char zstd_buffer[80]; +#endif + + (void)stamp; /* avoid compiler warnings, we don't use this */ + +#ifdef USE_SSL + Curl_ssl_version(ssl_buffer, sizeof(ssl_buffer)); + version_info.ssl_version = ssl_buffer; +#endif + +#ifdef HAVE_LIBZ + version_info.libz_version = zlibVersion(); + /* libz left NULL if non-existing */ +#endif +#ifdef USE_ARES + { + int aresnum; + version_info.ares = ares_version(&aresnum); + version_info.ares_num = aresnum; + } +#endif +#ifdef USE_LIBIDN2 + /* This returns a version string if we use the given version or later, + otherwise it returns NULL */ + version_info.libidn = idn2_check_version(IDN2_VERSION); +#endif + +#if defined(USE_SSH) + Curl_ssh_version(ssh_buffer, sizeof(ssh_buffer)); + version_info.libssh_version = ssh_buffer; +#endif + +#ifdef HAVE_BROTLI + version_info.brotli_ver_num = BrotliDecoderVersion(); + brotli_version(brotli_buffer, sizeof(brotli_buffer)); + version_info.brotli_version = brotli_buffer; +#endif + +#ifdef HAVE_ZSTD + version_info.zstd_ver_num = (unsigned int)ZSTD_versionNumber(); + zstd_version(zstd_buffer, sizeof(zstd_buffer)); + version_info.zstd_version = zstd_buffer; +#endif + +#ifdef USE_NGHTTP2 + { + nghttp2_info *h2 = nghttp2_version(0); + version_info.nghttp2_ver_num = h2->version_num; + version_info.nghttp2_version = h2->version_str; + } +#endif + +#ifdef ENABLE_QUIC + { + static char quicbuffer[80]; + Curl_quic_ver(quicbuffer, sizeof(quicbuffer)); + version_info.quic_version = quicbuffer; + } +#endif + +#ifdef USE_HYPER + { + static char hyper_buffer[30]; + msnprintf(hyper_buffer, sizeof(hyper_buffer), "Hyper/%s", hyper_version()); + version_info.hyper_version = hyper_buffer; + } +#endif + +#ifdef USE_GSASL + { + version_info.gsasl_version = gsasl_check_version(NULL); + } +#endif + + /* Get available features, build bitmask and names array. */ + n = 0; + for(p = features_table; p->name; p++) + if(!p->present || p->present(&version_info)) { + features |= p->bitmask; + feature_names[n++] = p->name; + } + + feature_names[n] = NULL; /* Terminate array. */ + version_info.features = features; + + return &version_info; +} diff --git a/Utilities/cmcurl/lib/version_win32.c b/Utilities/cmcurl/lib/version_win32.c new file mode 100644 index 0000000..e0f239e --- /dev/null +++ b/Utilities/cmcurl/lib/version_win32.c @@ -0,0 +1,319 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * + * 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(_WIN32) + +#include <curl/curl.h> +#include "version_win32.h" +#include "warnless.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* This Unicode version struct works for VerifyVersionInfoW (OSVERSIONINFOEXW) + and RtlVerifyVersionInfo (RTLOSVERSIONINFOEXW) */ +struct OUR_OSVERSIONINFOEXW { + ULONG dwOSVersionInfoSize; + ULONG dwMajorVersion; + ULONG dwMinorVersion; + ULONG dwBuildNumber; + ULONG dwPlatformId; + WCHAR szCSDVersion[128]; + USHORT wServicePackMajor; + USHORT wServicePackMinor; + USHORT wSuiteMask; + UCHAR wProductType; + UCHAR wReserved; +}; + +/* + * curlx_verify_windows_version() + * + * This is used to verify if we are running on a specific windows version. + * + * Parameters: + * + * majorVersion [in] - The major version number. + * minorVersion [in] - The minor version number. + * buildVersion [in] - The build version number. If 0, this parameter is + * ignored. + * platform [in] - The optional platform identifier. + * condition [in] - The test condition used to specifier whether we are + * checking a version less then, equal to or greater than + * what is specified in the major and minor version + * numbers. + * + * Returns TRUE if matched; otherwise FALSE. + */ +bool curlx_verify_windows_version(const unsigned int majorVersion, + const unsigned int minorVersion, + const unsigned int buildVersion, + const PlatformIdentifier platform, + const VersionCondition condition) +{ + bool matched = FALSE; + +#if defined(CURL_WINDOWS_APP) + (void)buildVersion; + + /* We have no way to determine the Windows version from Windows apps, + so let's assume we're running on the target Windows version. */ + const WORD fullVersion = MAKEWORD(minorVersion, majorVersion); + const WORD targetVersion = (WORD)_WIN32_WINNT; + + switch(condition) { + case VERSION_LESS_THAN: + matched = targetVersion < fullVersion; + break; + + case VERSION_LESS_THAN_EQUAL: + matched = targetVersion <= fullVersion; + break; + + case VERSION_EQUAL: + matched = targetVersion == fullVersion; + break; + + case VERSION_GREATER_THAN_EQUAL: + matched = targetVersion >= fullVersion; + break; + + case VERSION_GREATER_THAN: + matched = targetVersion > fullVersion; + break; + } + + if(matched && (platform == PLATFORM_WINDOWS)) { + /* we're always running on PLATFORM_WINNT */ + matched = FALSE; + } +#elif !defined(_WIN32_WINNT) || !defined(_WIN32_WINNT_WIN2K) || \ + (_WIN32_WINNT < _WIN32_WINNT_WIN2K) + OSVERSIONINFO osver; + + memset(&osver, 0, sizeof(osver)); + osver.dwOSVersionInfoSize = sizeof(osver); + + /* Find out Windows version */ + if(GetVersionEx(&osver)) { + /* Verify the Operating System version number */ + switch(condition) { + case VERSION_LESS_THAN: + if(osver.dwMajorVersion < majorVersion || + (osver.dwMajorVersion == majorVersion && + osver.dwMinorVersion < minorVersion) || + (buildVersion != 0 && + (osver.dwMajorVersion == majorVersion && + osver.dwMinorVersion == minorVersion && + osver.dwBuildNumber < buildVersion))) + matched = TRUE; + break; + + case VERSION_LESS_THAN_EQUAL: + if(osver.dwMajorVersion < majorVersion || + (osver.dwMajorVersion == majorVersion && + osver.dwMinorVersion < minorVersion) || + (osver.dwMajorVersion == majorVersion && + osver.dwMinorVersion == minorVersion && + (buildVersion == 0 || + osver.dwBuildNumber <= buildVersion))) + matched = TRUE; + break; + + case VERSION_EQUAL: + if(osver.dwMajorVersion == majorVersion && + osver.dwMinorVersion == minorVersion && + (buildVersion == 0 || + osver.dwBuildNumber == buildVersion)) + matched = TRUE; + break; + + case VERSION_GREATER_THAN_EQUAL: + if(osver.dwMajorVersion > majorVersion || + (osver.dwMajorVersion == majorVersion && + osver.dwMinorVersion > minorVersion) || + (osver.dwMajorVersion == majorVersion && + osver.dwMinorVersion == minorVersion && + (buildVersion == 0 || + osver.dwBuildNumber >= buildVersion))) + matched = TRUE; + break; + + case VERSION_GREATER_THAN: + if(osver.dwMajorVersion > majorVersion || + (osver.dwMajorVersion == majorVersion && + osver.dwMinorVersion > minorVersion) || + (buildVersion != 0 && + (osver.dwMajorVersion == majorVersion && + osver.dwMinorVersion == minorVersion && + osver.dwBuildNumber > buildVersion))) + matched = TRUE; + break; + } + + /* Verify the platform identifier (if necessary) */ + if(matched) { + switch(platform) { + case PLATFORM_WINDOWS: + if(osver.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) + matched = FALSE; + break; + + case PLATFORM_WINNT: + if(osver.dwPlatformId != VER_PLATFORM_WIN32_NT) + matched = FALSE; + break; + + default: /* like platform == PLATFORM_DONT_CARE */ + break; + } + } + } +#else + ULONGLONG cm = 0; + struct OUR_OSVERSIONINFOEXW osver; + BYTE majorCondition; + BYTE minorCondition; + BYTE buildCondition; + BYTE spMajorCondition; + BYTE spMinorCondition; + DWORD dwTypeMask = VER_MAJORVERSION | VER_MINORVERSION | + VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR; + + typedef LONG (APIENTRY *RTLVERIFYVERSIONINFO_FN) + (struct OUR_OSVERSIONINFOEXW *, ULONG, ULONGLONG); + static RTLVERIFYVERSIONINFO_FN pRtlVerifyVersionInfo; + static bool onetime = true; /* safe because first call is during init */ + + if(onetime) { + pRtlVerifyVersionInfo = CURLX_FUNCTION_CAST(RTLVERIFYVERSIONINFO_FN, + (GetProcAddress(GetModuleHandleA("ntdll"), "RtlVerifyVersionInfo"))); + onetime = false; + } + + switch(condition) { + case VERSION_LESS_THAN: + majorCondition = VER_LESS; + minorCondition = VER_LESS; + buildCondition = VER_LESS; + spMajorCondition = VER_LESS_EQUAL; + spMinorCondition = VER_LESS_EQUAL; + break; + + case VERSION_LESS_THAN_EQUAL: + majorCondition = VER_LESS_EQUAL; + minorCondition = VER_LESS_EQUAL; + buildCondition = VER_LESS_EQUAL; + spMajorCondition = VER_LESS_EQUAL; + spMinorCondition = VER_LESS_EQUAL; + break; + + case VERSION_EQUAL: + majorCondition = VER_EQUAL; + minorCondition = VER_EQUAL; + buildCondition = VER_EQUAL; + spMajorCondition = VER_GREATER_EQUAL; + spMinorCondition = VER_GREATER_EQUAL; + break; + + case VERSION_GREATER_THAN_EQUAL: + majorCondition = VER_GREATER_EQUAL; + minorCondition = VER_GREATER_EQUAL; + buildCondition = VER_GREATER_EQUAL; + spMajorCondition = VER_GREATER_EQUAL; + spMinorCondition = VER_GREATER_EQUAL; + break; + + case VERSION_GREATER_THAN: + majorCondition = VER_GREATER; + minorCondition = VER_GREATER; + buildCondition = VER_GREATER; + spMajorCondition = VER_GREATER_EQUAL; + spMinorCondition = VER_GREATER_EQUAL; + break; + + default: + return FALSE; + } + + memset(&osver, 0, sizeof(osver)); + osver.dwOSVersionInfoSize = sizeof(osver); + osver.dwMajorVersion = majorVersion; + osver.dwMinorVersion = minorVersion; + osver.dwBuildNumber = buildVersion; + if(platform == PLATFORM_WINDOWS) + osver.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; + else if(platform == PLATFORM_WINNT) + osver.dwPlatformId = VER_PLATFORM_WIN32_NT; + + cm = VerSetConditionMask(cm, VER_MAJORVERSION, majorCondition); + cm = VerSetConditionMask(cm, VER_MINORVERSION, minorCondition); + cm = VerSetConditionMask(cm, VER_SERVICEPACKMAJOR, spMajorCondition); + cm = VerSetConditionMask(cm, VER_SERVICEPACKMINOR, spMinorCondition); + + if(platform != PLATFORM_DONT_CARE) { + cm = VerSetConditionMask(cm, VER_PLATFORMID, VER_EQUAL); + dwTypeMask |= VER_PLATFORMID; + } + + /* Later versions of Windows have version functions that may not return the + real version of Windows unless the application is so manifested. We prefer + the real version always, so we use the Rtl variant of the function when + possible. Note though the function signatures have underlying fundamental + types that are the same, the return values are different. */ + if(pRtlVerifyVersionInfo) + matched = !pRtlVerifyVersionInfo(&osver, dwTypeMask, cm); + else + matched = !!VerifyVersionInfoW((OSVERSIONINFOEXW *)&osver, dwTypeMask, cm); + + /* Compare the build number separately. VerifyVersionInfo normally compares + major.minor in hierarchical order (eg 1.9 is less than 2.0) but does not + do the same for build (eg 1.9 build 222 is not less than 2.0 build 111). + Build comparison is only needed when build numbers are equal (eg 1.9 is + always less than 2.0 so build comparison is not needed). */ + if(matched && buildVersion && + (condition == VERSION_EQUAL || + ((condition == VERSION_GREATER_THAN_EQUAL || + condition == VERSION_LESS_THAN_EQUAL) && + curlx_verify_windows_version(majorVersion, minorVersion, 0, + platform, VERSION_EQUAL)))) { + + cm = VerSetConditionMask(0, VER_BUILDNUMBER, buildCondition); + dwTypeMask = VER_BUILDNUMBER; + if(pRtlVerifyVersionInfo) + matched = !pRtlVerifyVersionInfo(&osver, dwTypeMask, cm); + else + matched = !!VerifyVersionInfoW((OSVERSIONINFOEXW *)&osver, + dwTypeMask, cm); + } + +#endif + + return matched; +} + +#endif /* _WIN32 */ diff --git a/Utilities/cmcurl/lib/version_win32.h b/Utilities/cmcurl/lib/version_win32.h new file mode 100644 index 0000000..95c0661 --- /dev/null +++ b/Utilities/cmcurl/lib/version_win32.h @@ -0,0 +1,56 @@ +#ifndef HEADER_CURL_VERSION_WIN32_H +#define HEADER_CURL_VERSION_WIN32_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * + * 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(_WIN32) + +/* Version condition */ +typedef enum { + VERSION_LESS_THAN, + VERSION_LESS_THAN_EQUAL, + VERSION_EQUAL, + VERSION_GREATER_THAN_EQUAL, + VERSION_GREATER_THAN +} VersionCondition; + +/* Platform identifier */ +typedef enum { + PLATFORM_DONT_CARE, + PLATFORM_WINDOWS, + PLATFORM_WINNT +} PlatformIdentifier; + +/* This is used to verify if we are running on a specific windows version */ +bool curlx_verify_windows_version(const unsigned int majorVersion, + const unsigned int minorVersion, + const unsigned int buildVersion, + const PlatformIdentifier platform, + const VersionCondition condition); + +#endif /* _WIN32 */ + +#endif /* HEADER_CURL_VERSION_WIN32_H */ diff --git a/Utilities/cmcurl/lib/vquic/curl_msh3.c b/Utilities/cmcurl/lib/vquic/curl_msh3.c new file mode 100644 index 0000000..8ae3672 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_msh3.c @@ -0,0 +1,1092 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_MSH3 + +#include "urldata.h" +#include "timeval.h" +#include "multiif.h" +#include "sendf.h" +#include "curl_trc.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "connect.h" +#include "progress.h" +#include "http1.h" +#include "curl_msh3.h" +#include "socketpair.h" +#include "vtls/vtls.h" +#include "vquic/vquic.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef CURL_DISABLE_SOCKETPAIR +#error "MSH3 cannot be build with CURL_DISABLE_SOCKETPAIR set" +#endif + +#define H3_STREAM_WINDOW_SIZE (128 * 1024) +#define H3_STREAM_CHUNK_SIZE (16 * 1024) +#define H3_STREAM_RECV_CHUNKS \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) + +#ifdef _WIN32 +#define msh3_lock CRITICAL_SECTION +#define msh3_lock_initialize(lock) InitializeCriticalSection(lock) +#define msh3_lock_uninitialize(lock) DeleteCriticalSection(lock) +#define msh3_lock_acquire(lock) EnterCriticalSection(lock) +#define msh3_lock_release(lock) LeaveCriticalSection(lock) +#else /* !_WIN32 */ +#include <pthread.h> +#define msh3_lock pthread_mutex_t +#define msh3_lock_initialize(lock) do { \ + pthread_mutexattr_t attr; \ + pthread_mutexattr_init(&attr); \ + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \ + pthread_mutex_init(lock, &attr); \ + pthread_mutexattr_destroy(&attr); \ +}while(0) +#define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock) +#define msh3_lock_acquire(lock) pthread_mutex_lock(lock) +#define msh3_lock_release(lock) pthread_mutex_unlock(lock) +#endif /* _WIN32 */ + + +static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection, + void *IfContext); +static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection, + void *IfContext); +static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection, + void *IfContext, + MSH3_REQUEST *Request); +static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, + void *IfContext, + const MSH3_HEADER *Header); +static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, + void *IfContext, uint32_t *Length, + const uint8_t *Data); +static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, + bool Aborted, uint64_t AbortError); +static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request, + void *IfContext); +static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request, + void *IfContext, void *SendContext); + + +void Curl_msh3_ver(char *p, size_t len) +{ + uint32_t v[4]; + MsH3Version(v); + (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]); +} + +#define SP_LOCAL 0 +#define SP_REMOTE 1 + +struct cf_msh3_ctx { + MSH3_API *api; + MSH3_CONNECTION *qconn; + struct Curl_sockaddr_ex addr; + curl_socket_t sock[2]; /* fake socket pair until we get support in msh3 */ + char l_ip[MAX_IPADR_LEN]; /* local IP as string */ + int l_port; /* local port number */ + struct cf_call_data call_data; + struct curltime connect_started; /* time the current attempt started */ + struct curltime handshake_at; /* time connect handshake finished */ + /* Flags written by msh3/msquic thread */ + bool handshake_complete; + bool handshake_succeeded; + bool connected; + /* Flags written by curl thread */ + BIT(verbose); + BIT(active); +}; + +/* How to access `call_data` from a cf_msh3 filter */ +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) \ + ((struct cf_msh3_ctx *)(cf)->ctx)->call_data + +/** + * All about the H3 internals of a stream + */ +struct stream_ctx { + struct MSH3_REQUEST *req; + struct bufq recvbuf; /* h3 response */ +#ifdef _WIN32 + CRITICAL_SECTION recv_lock; +#else /* !_WIN32 */ + pthread_mutex_t recv_lock; +#endif /* _WIN32 */ + uint64_t error3; /* HTTP/3 stream error code */ + int status_code; /* HTTP status code */ + CURLcode recv_error; + bool closed; + bool reset; + bool upload_done; + bool firstheader; /* FALSE until headers arrive */ + bool recv_header_complete; +}; + +#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ + ((struct HTTP *)(d)->req.p.http)->h3_ctx \ + : NULL)) +#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx +#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ + H3_STREAM_CTX(d)->id : -2) + + +static CURLcode h3_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + + if(stream) + return CURLE_OK; + + stream = calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + H3_STREAM_LCTX(data) = stream; + stream->req = ZERO_NULL; + msh3_lock_initialize(&stream->recv_lock); + Curl_bufq_init2(&stream->recvbuf, H3_STREAM_CHUNK_SIZE, + H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + CURL_TRC_CF(data, cf, "data setup"); + return CURLE_OK; +} + +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "easy handle is done"); + Curl_bufq_free(&stream->recvbuf); + free(stream); + H3_STREAM_LCTX(data) = NULL; + } +} + +static void drain_stream_from_other_thread(struct Curl_easy *data, + struct stream_ctx *stream) +{ + unsigned char bits; + + /* risky */ + bits = CURL_CSELECT_IN; + if(stream && !stream->upload_done) + bits |= CURL_CSELECT_OUT; + if(data->state.dselect_bits != bits) { + data->state.dselect_bits = bits; + /* cannot expire from other thread */ + } +} + +static void drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(stream && !stream->upload_done) + bits |= CURL_CSELECT_OUT; + if(data->state.dselect_bits != bits) { + data->state.dselect_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + +static const MSH3_CONNECTION_IF msh3_conn_if = { + msh3_conn_connected, + msh3_conn_shutdown_complete, + msh3_conn_new_request +}; + +static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection, + void *IfContext) +{ + struct Curl_cfilter *cf = IfContext; + struct cf_msh3_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + (void)Connection; + + CURL_TRC_CF(data, cf, "[MSH3] connected"); + ctx->handshake_succeeded = true; + ctx->connected = true; + ctx->handshake_complete = true; +} + +static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection, + void *IfContext) +{ + struct Curl_cfilter *cf = IfContext; + struct cf_msh3_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + (void)Connection; + CURL_TRC_CF(data, cf, "[MSH3] shutdown complete"); + ctx->connected = false; + ctx->handshake_complete = true; +} + +static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection, + void *IfContext, + MSH3_REQUEST *Request) +{ + (void)Connection; + (void)IfContext; + (void)Request; +} + +static const MSH3_REQUEST_IF msh3_request_if = { + msh3_header_received, + msh3_data_received, + msh3_complete, + msh3_shutdown_complete, + msh3_data_sent +}; + +/* Decode HTTP status code. Returns -1 if no valid status code was + decoded. (duplicate from http2.c) */ +static int decode_status_code(const char *value, size_t len) +{ + int i; + int res; + + if(len != 3) { + return -1; + } + + res = 0; + + for(i = 0; i < 3; ++i) { + char c = value[i]; + + if(c < '0' || c > '9') { + return -1; + } + + res *= 10; + res += c - '0'; + } + + return res; +} + +/* + * write_resp_raw() copies response data in raw format to the `data`'s + * receive buffer. If not enough space is available, it appends to the + * `data`'s overflow buffer. + */ +static CURLcode write_resp_raw(struct Curl_easy *data, + const void *mem, size_t memlen) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result = CURLE_OK; + ssize_t nwritten; + + if(!stream) + return CURLE_RECV_ERROR; + + nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result); + if(nwritten < 0) { + return result; + } + + if((size_t)nwritten < memlen) { + /* This MUST not happen. Our recbuf is dimensioned to hold the + * full max_stream_window and then some for this very reason. */ + DEBUGASSERT(0); + return CURLE_RECV_ERROR; + } + return result; +} + +static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, + void *userp, + const MSH3_HEADER *hd) +{ + struct Curl_easy *data = userp; + struct stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result; + (void)Request; + + if(!stream || stream->recv_header_complete) { + return; + } + + msh3_lock_acquire(&stream->recv_lock); + + if((hd->NameLength == 7) && + !strncmp(HTTP_PSEUDO_STATUS, (char *)hd->Name, 7)) { + char line[14]; /* status line is always 13 characters long */ + size_t ncopy; + + DEBUGASSERT(!stream->firstheader); + stream->status_code = decode_status_code(hd->Value, hd->ValueLength); + DEBUGASSERT(stream->status_code != -1); + ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", + stream->status_code); + result = write_resp_raw(data, line, ncopy); + if(result) + stream->recv_error = result; + stream->firstheader = TRUE; + } + else { + /* store as an HTTP1-style header */ + DEBUGASSERT(stream->firstheader); + result = write_resp_raw(data, hd->Name, hd->NameLength); + if(!result) + result = write_resp_raw(data, ": ", 2); + if(!result) + result = write_resp_raw(data, hd->Value, hd->ValueLength); + if(!result) + result = write_resp_raw(data, "\r\n", 2); + if(result) { + stream->recv_error = result; + } + } + + drain_stream_from_other_thread(data, stream); + msh3_lock_release(&stream->recv_lock); +} + +static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, + void *IfContext, uint32_t *buflen, + const uint8_t *buf) +{ + struct Curl_easy *data = IfContext; + struct stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result; + bool rv = FALSE; + + /* TODO: we would like to limit the amount of data we are buffer here. + * There seems to be no mechanism in msh3 to adjust flow control and + * it is undocumented what happens if we return FALSE here or less + * length (buflen is an inout parameter). + */ + (void)Request; + if(!stream) + return FALSE; + + msh3_lock_acquire(&stream->recv_lock); + + if(!stream->recv_header_complete) { + result = write_resp_raw(data, "\r\n", 2); + if(result) { + stream->recv_error = result; + goto out; + } + stream->recv_header_complete = true; + } + + result = write_resp_raw(data, buf, *buflen); + if(result) { + stream->recv_error = result; + } + rv = TRUE; + +out: + msh3_lock_release(&stream->recv_lock); + return rv; +} + +static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, + bool aborted, uint64_t error) +{ + struct Curl_easy *data = IfContext; + struct stream_ctx *stream = H3_STREAM_CTX(data); + + (void)Request; + if(!stream) + return; + msh3_lock_acquire(&stream->recv_lock); + stream->closed = TRUE; + stream->recv_header_complete = true; + if(error) + stream->error3 = error; + if(aborted) + stream->reset = TRUE; + msh3_lock_release(&stream->recv_lock); +} + +static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request, + void *IfContext) +{ + struct Curl_easy *data = IfContext; + struct stream_ctx *stream = H3_STREAM_CTX(data); + + if(!stream) + return; + (void)Request; + (void)stream; +} + +static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request, + void *IfContext, void *SendContext) +{ + struct Curl_easy *data = IfContext; + struct stream_ctx *stream = H3_STREAM_CTX(data); + if(!stream) + return; + (void)Request; + (void)stream; + (void)SendContext; +} + +static ssize_t recv_closed_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + CURLcode *err) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + ssize_t nread = -1; + + if(!stream) { + *err = CURLE_RECV_ERROR; + return -1; + } + (void)cf; + if(stream->reset) { + failf(data, "HTTP/3 stream reset by server"); + *err = CURLE_PARTIAL_FILE; + CURL_TRC_CF(data, cf, "cf_recv, was reset -> %d", *err); + goto out; + } + else if(stream->error3) { + failf(data, "HTTP/3 stream was not closed cleanly: (error %zd)", + (ssize_t)stream->error3); + *err = CURLE_HTTP3; + CURL_TRC_CF(data, cf, "cf_recv, closed uncleanly -> %d", *err); + goto out; + } + else { + CURL_TRC_CF(data, cf, "cf_recv, closed ok -> %d", *err); + } + *err = CURLE_OK; + nread = 0; + +out: + return nread; +} + +static void set_quic_expire(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + + /* we have no indication from msh3 when it would be a good time + * to juggle the connection again. So, we compromise by calling + * us again every some milliseconds. */ + (void)cf; + if(stream && stream->req && !stream->closed) { + Curl_expire(data, 10, EXPIRE_QUIC); + } + else { + Curl_expire(data, 50, EXPIRE_QUIC); + } +} + +static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + ssize_t nread = -1; + struct cf_call_data save; + + (void)cf; + if(!stream) { + *err = CURLE_RECV_ERROR; + return -1; + } + CF_DATA_SAVE(save, cf, data); + CURL_TRC_CF(data, cf, "req: recv with %zu byte buffer", len); + + msh3_lock_acquire(&stream->recv_lock); + + if(stream->recv_error) { + failf(data, "request aborted"); + *err = stream->recv_error; + goto out; + } + + *err = CURLE_OK; + + if(!Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + CURL_TRC_CF(data, cf, "read recvbuf(len=%zu) -> %zd, %d", + len, nread, *err); + if(nread < 0) + goto out; + if(stream->closed) + drain_stream(cf, data); + } + else if(stream->closed) { + nread = recv_closed_stream(cf, data, err); + goto out; + } + else { + CURL_TRC_CF(data, cf, "req: nothing here, call again"); + *err = CURLE_AGAIN; + } + +out: + msh3_lock_release(&stream->recv_lock); + set_quic_expire(cf, data); + CF_DATA_RESTORE(cf, save); + return nread; +} + +static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h1_req_parser h1; + struct dynhds h2_headers; + MSH3_HEADER *nva = NULL; + size_t nheader, i; + ssize_t nwritten = -1; + struct cf_call_data save; + bool eos; + + CF_DATA_SAVE(save, cf, data); + + Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + + /* Sizes must match for cast below to work" */ + DEBUGASSERT(stream); + CURL_TRC_CF(data, cf, "req: send %zu bytes", len); + + if(!stream->req) { + /* The first send on the request contains the headers and possibly some + data. Parse out the headers and create the request, then if there is + any data left over go ahead and send it too. */ + nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err); + if(nwritten < 0) + goto out; + DEBUGASSERT(h1.done); + DEBUGASSERT(h1.req); + + *err = Curl_http_req_to_h2(&h2_headers, h1.req, data); + if(*err) { + nwritten = -1; + goto out; + } + + nheader = Curl_dynhds_count(&h2_headers); + nva = malloc(sizeof(MSH3_HEADER) * nheader); + if(!nva) { + *err = CURLE_OUT_OF_MEMORY; + nwritten = -1; + goto out; + } + + for(i = 0; i < nheader; ++i) { + struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); + nva[i].Name = e->name; + nva[i].NameLength = e->namelen; + nva[i].Value = e->value; + nva[i].ValueLength = e->valuelen; + } + + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + case HTTPREQ_PUT: + /* known request body size or -1 */ + eos = FALSE; + break; + default: + /* there is not request body */ + eos = TRUE; + stream->upload_done = TRUE; + break; + } + + CURL_TRC_CF(data, cf, "req: send %zu headers", nheader); + stream->req = MsH3RequestOpen(ctx->qconn, &msh3_request_if, data, + nva, nheader, + eos ? MSH3_REQUEST_FLAG_FIN : + MSH3_REQUEST_FLAG_NONE); + if(!stream->req) { + failf(data, "request open failed"); + *err = CURLE_SEND_ERROR; + goto out; + } + *err = CURLE_OK; + nwritten = len; + goto out; + } + else { + /* request is open */ + CURL_TRC_CF(data, cf, "req: send %zu body bytes", len); + if(len > 0xFFFFFFFF) { + len = 0xFFFFFFFF; + } + + if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_NONE, buf, + (uint32_t)len, stream)) { + *err = CURLE_SEND_ERROR; + goto out; + } + + /* TODO - msh3/msquic will hold onto this memory until the send complete + event. How do we make sure curl doesn't free it until then? */ + *err = CURLE_OK; + nwritten = len; + } + +out: + set_quic_expire(cf, data); + free(nva); + Curl_h1_req_parse_free(&h1); + Curl_dynhds_free(&h2_headers); + CF_DATA_RESTORE(cf, save); + return nwritten; +} + +static void cf_msh3_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + if(stream && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) { + if(stream->recv_error) { + Curl_pollset_add_in(data, ps, ctx->sock[SP_LOCAL]); + drain_stream(cf, data); + } + else if(stream->req) { + Curl_pollset_add_out(data, ps, ctx->sock[SP_LOCAL]); + drain_stream(cf, data); + } + } +} + +static bool cf_msh3_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_call_data save; + bool pending = FALSE; + + CF_DATA_SAVE(save, cf, data); + + (void)cf; + if(stream && stream->req) { + msh3_lock_acquire(&stream->recv_lock); + CURL_TRC_CF((struct Curl_easy *)data, cf, "data pending = %zu", + Curl_bufq_len(&stream->recvbuf)); + pending = !Curl_bufq_is_empty(&stream->recvbuf); + msh3_lock_release(&stream->recv_lock); + if(pending) + drain_stream(cf, (struct Curl_easy *)data); + } + + CF_DATA_RESTORE(cf, save); + return pending; +} + +static void cf_msh3_active(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + + /* use this socket from now on */ + cf->conn->sock[cf->sockindex] = ctx->sock[SP_LOCAL]; + /* the first socket info gets set at conn and data */ + if(cf->sockindex == FIRSTSOCKET) { + cf->conn->remote_addr = &ctx->addr; + #ifdef ENABLE_IPV6 + cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE; + #endif + Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); + } + ctx->active = TRUE; +} + +static CURLcode h3_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ + if(!pause) { + drain_stream(cf, data); + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + return CURLE_OK; +} + +static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_call_data save; + CURLcode result = CURLE_OK; + + CF_DATA_SAVE(save, cf, data); + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_DATA_SETUP: + result = h3_data_setup(cf, data); + break; + case CF_CTRL_DATA_PAUSE: + result = h3_data_pause(cf, data, (arg1 != 0)); + break; + case CF_CTRL_DATA_DONE: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE_SEND: + CURL_TRC_CF(data, cf, "req: send done"); + if(stream) { + stream->upload_done = TRUE; + if(stream->req) { + char buf[1]; + if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_FIN, + buf, 0, data)) { + result = CURLE_SEND_ERROR; + } + } + } + break; + case CF_CTRL_CONN_INFO_UPDATE: + CURL_TRC_CF(data, cf, "req: update info"); + cf_msh3_active(cf, data); + break; + default: + break; + } + + CF_DATA_RESTORE(cf, save); + return result; +} + +static CURLcode cf_connect_start(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct ssl_primary_config *conn_config; + MSH3_ADDR addr = {0}; + CURLcode result; + bool verify; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + verify = !!conn_config->verifypeer; + + memcpy(&addr, &ctx->addr.sa_addr, ctx->addr.addrlen); + MSH3_SET_PORT(&addr, (uint16_t)cf->conn->remote_port); + + if(verify && (conn_config->CAfile || conn_config->CApath)) { + /* TODO: need a way to provide trust anchors to MSH3 */ +#ifdef DEBUGBUILD + /* we need this for our test cases to run */ + CURL_TRC_CF(data, cf, "non-standard CA not supported, " + "switching off verifypeer in DEBUG mode"); + verify = 0; +#else + CURL_TRC_CF(data, cf, "non-standard CA not supported, " + "attempting with built-in verification"); +#endif + } + + CURL_TRC_CF(data, cf, "connecting to %s:%d (verify=%d)", + cf->conn->host.name, (int)cf->conn->remote_port, verify); + + ctx->api = MsH3ApiOpen(); + if(!ctx->api) { + failf(data, "can't create msh3 api"); + return CURLE_FAILED_INIT; + } + + ctx->qconn = MsH3ConnectionOpen(ctx->api, + &msh3_conn_if, + cf, + cf->conn->host.name, + &addr, + !verify); + if(!ctx->qconn) { + failf(data, "can't create msh3 connection"); + if(ctx->api) { + MsH3ApiClose(ctx->api); + ctx->api = NULL; + } + return CURLE_FAILED_INIT; + } + + result = h3_data_setup(cf, data); + if(result) + return result; + + return CURLE_OK; +} + +static CURLcode cf_msh3_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result = CURLE_OK; + + (void)blocking; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + + if(ctx->sock[SP_LOCAL] == CURL_SOCKET_BAD) { + if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx->sock[0]) < 0) { + ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; + ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; + return CURLE_COULDNT_CONNECT; + } + } + + *done = FALSE; + if(!ctx->qconn) { + ctx->connect_started = Curl_now(); + result = cf_connect_start(cf, data); + if(result) + goto out; + } + + if(ctx->handshake_complete) { + ctx->handshake_at = Curl_now(); + if(ctx->handshake_succeeded) { + CURL_TRC_CF(data, cf, "handshake succeeded"); + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; + cf->connected = TRUE; + cf->conn->alpn = CURL_HTTP_VERSION_3; + *done = TRUE; + connkeep(cf->conn, "HTTP/3 default"); + Curl_pgrsTime(data, TIMER_APPCONNECT); + } + else { + failf(data, "failed to connect, handshake failed"); + result = CURLE_COULDNT_CONNECT; + } + } + +out: + CF_DATA_RESTORE(cf, save); + return result; +} + +static void cf_msh3_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct cf_call_data save; + + (void)data; + CF_DATA_SAVE(save, cf, data); + + if(ctx) { + CURL_TRC_CF(data, cf, "destroying"); + if(ctx->qconn) { + MsH3ConnectionClose(ctx->qconn); + ctx->qconn = NULL; + } + if(ctx->api) { + MsH3ApiClose(ctx->api); + ctx->api = NULL; + } + + if(ctx->active) { + /* We share our socket at cf->conn->sock[cf->sockindex] when active. + * If it is no longer there, someone has stolen (and hopefully + * closed it) and we just forget about it. + */ + ctx->active = FALSE; + if(ctx->sock[SP_LOCAL] == cf->conn->sock[cf->sockindex]) { + CURL_TRC_CF(data, cf, "cf_msh3_close(%d) active", + (int)ctx->sock[SP_LOCAL]); + cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; + } + else { + CURL_TRC_CF(data, cf, "cf_socket_close(%d) no longer at " + "conn->sock[], discarding", (int)ctx->sock[SP_LOCAL]); + ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; + } + if(cf->sockindex == FIRSTSOCKET) + cf->conn->remote_addr = NULL; + } + if(ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) { + sclose(ctx->sock[SP_LOCAL]); + } + if(ctx->sock[SP_REMOTE] != CURL_SOCKET_BAD) { + sclose(ctx->sock[SP_REMOTE]); + } + ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; + ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; + } + CF_DATA_RESTORE(cf, save); +} + +static void cf_msh3_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + cf_msh3_close(cf, data); + free(cf->ctx); + cf->ctx = NULL; + /* no CF_DATA_RESTORE(cf, save); its gone */ + +} + +static CURLcode cf_msh3_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: { + /* TODO: we do not have access to this so far, fake it */ + (void)ctx; + *pres1 = 100; + return CURLE_OK; + } + case CF_QUERY_TIMER_CONNECT: { + struct curltime *when = pres2; + /* we do not know when the first byte arrived */ + if(cf->connected) + *when = ctx->handshake_at; + return CURLE_OK; + } + case CF_QUERY_TIMER_APPCONNECT: { + struct curltime *when = pres2; + if(cf->connected) + *when = ctx->handshake_at; + return CURLE_OK; + } + default: + break; + } + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +static bool cf_msh3_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + + (void)data; + *input_pending = FALSE; + return ctx && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD && ctx->qconn && + ctx->connected; +} + +struct Curl_cftype Curl_cft_http3 = { + "HTTP/3", + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX, + 0, + cf_msh3_destroy, + cf_msh3_connect, + cf_msh3_close, + Curl_cf_def_get_host, + cf_msh3_adjust_pollset, + cf_msh3_data_pending, + cf_msh3_send, + cf_msh3_recv, + cf_msh3_data_event, + cf_msh3_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_msh3_query, +}; + +CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai) +{ + struct cf_msh3_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + (void)data; + (void)conn; + (void)ai; /* TODO: msh3 resolves itself? */ + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC); + ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; + ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; + + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + +out: + *pcf = (!result)? cf : NULL; + if(result) { + Curl_safefree(cf); + Curl_safefree(ctx); + } + + return result; +} + +bool Curl_conn_is_msh3(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + + (void)data; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_http3) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT) + return FALSE; + } + return FALSE; +} + +#endif /* USE_MSH3 */ diff --git a/Utilities/cmcurl/lib/vquic/curl_msh3.h b/Utilities/cmcurl/lib/vquic/curl_msh3.h new file mode 100644 index 0000000..33931f5 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_msh3.h @@ -0,0 +1,46 @@ +#ifndef HEADER_CURL_VQUIC_CURL_MSH3_H +#define HEADER_CURL_VQUIC_CURL_MSH3_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_MSH3 + +#include <msh3.h> + +void Curl_msh3_ver(char *p, size_t len); + +CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai); + +bool Curl_conn_is_msh3(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); + +#endif /* USE_MSQUIC */ + +#endif /* HEADER_CURL_VQUIC_CURL_MSH3_H */ diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c new file mode 100644 index 0000000..f09b10b --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c @@ -0,0 +1,2835 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) +#include <ngtcp2/ngtcp2.h> +#include <nghttp3/nghttp3.h> + +#ifdef USE_OPENSSL +#include <openssl/err.h> +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +#include <ngtcp2/ngtcp2_crypto_boringssl.h> +#else +#include <ngtcp2/ngtcp2_crypto_quictls.h> +#endif +#include "vtls/openssl.h" +#elif defined(USE_GNUTLS) +#include <ngtcp2/ngtcp2_crypto_gnutls.h> +#include "vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include <ngtcp2/ngtcp2_crypto_wolfssl.h> +#include "vtls/wolfssl.h" +#endif + +#include "urldata.h" +#include "sendf.h" +#include "strdup.h" +#include "rand.h" +#include "multiif.h" +#include "strcase.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "connect.h" +#include "progress.h" +#include "strerror.h" +#include "dynbuf.h" +#include "http1.h" +#include "select.h" +#include "inet_pton.h" +#include "vquic.h" +#include "vquic_int.h" +#include "vtls/keylog.h" +#include "vtls/vtls.h" +#include "curl_ngtcp2.h" + +#include "warnless.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +#define H3_ALPN_H3_29 "\x5h3-29" +#define H3_ALPN_H3 "\x2h3" + +#define QUIC_MAX_STREAMS (256*1024) +#define QUIC_MAX_DATA (1*1024*1024) +#define QUIC_HANDSHAKE_TIMEOUT (10*NGTCP2_SECONDS) + +/* A stream window is the maximum amount we need to buffer for + * each active transfer. We use HTTP/3 flow control and only ACK + * when we take things out of the buffer. + * Chunk size is large enough to take a full DATA frame */ +#define H3_STREAM_WINDOW_SIZE (128 * 1024) +#define H3_STREAM_CHUNK_SIZE (16 * 1024) +/* The pool keeps spares around and half of a full stream windows + * seems good. More does not seem to improve performance. + * The benefit of the pool is that stream buffer to not keep + * spares. So memory consumption goes down when streams run empty, + * have a large upload done, etc. */ +#define H3_STREAM_POOL_SPARES \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE ) / 2 +/* Receive and Send max number of chunks just follows from the + * chunk size and window size */ +#define H3_STREAM_RECV_CHUNKS \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) +#define H3_STREAM_SEND_CHUNKS \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) + + +#ifdef USE_OPENSSL +#define QUIC_CIPHERS \ + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ + "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" +#define QUIC_GROUPS "P-256:X25519:P-384:P-521" +#elif defined(USE_GNUTLS) +#define QUIC_PRIORITY \ + "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ + "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ + "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ + "%DISABLE_TLS13_COMPAT_MODE" +#elif defined(USE_WOLFSSL) +#define QUIC_CIPHERS \ + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ + "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" +#define QUIC_GROUPS "P-256:P-384:P-521" +#endif + + +/* + * Store ngtcp2 version info in this buffer. + */ +void Curl_ngtcp2_ver(char *p, size_t len) +{ + const ngtcp2_info *ng2 = ngtcp2_version(0); + const nghttp3_info *ht3 = nghttp3_version(0); + (void)msnprintf(p, len, "ngtcp2/%s nghttp3/%s", + ng2->version_str, ht3->version_str); +} + +struct cf_ngtcp2_ctx { + struct cf_quic_ctx q; + struct ssl_peer peer; + ngtcp2_path connected_path; + ngtcp2_conn *qconn; + ngtcp2_cid dcid; + ngtcp2_cid scid; + uint32_t version; + ngtcp2_settings settings; + ngtcp2_transport_params transport_params; + ngtcp2_ccerr last_error; + ngtcp2_crypto_conn_ref conn_ref; +#ifdef USE_OPENSSL + SSL_CTX *sslctx; + SSL *ssl; +#elif defined(USE_GNUTLS) + struct gtls_instance *gtls; +#elif defined(USE_WOLFSSL) + WOLFSSL_CTX *sslctx; + WOLFSSL *ssl; +#endif + struct cf_call_data call_data; + nghttp3_conn *h3conn; + nghttp3_settings h3settings; + struct curltime started_at; /* time the current attempt started */ + struct curltime handshake_at; /* time connect handshake finished */ + struct curltime first_byte_at; /* when first byte was recvd */ + struct curltime reconnect_at; /* time the next attempt should start */ + struct bufc_pool stream_bufcp; /* chunk pool for streams */ + size_t max_stream_window; /* max flow window for one stream */ + uint64_t max_idle_ms; /* max idle time for QUIC connection */ + int qlogfd; + BIT(got_first_byte); /* if first byte was received */ +#ifdef USE_OPENSSL + BIT(x509_store_setup); /* if x509 store has been set up */ +#endif +}; + +/* How to access `call_data` from a cf_ngtcp2 filter */ +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) \ + ((struct cf_ngtcp2_ctx *)(cf)->ctx)->call_data + +/** + * All about the H3 internals of a stream + */ +struct h3_stream_ctx { + int64_t id; /* HTTP/3 protocol identifier */ + struct bufq sendbuf; /* h3 request body */ + struct bufq recvbuf; /* h3 response body */ + struct h1_req_parser h1; /* h1 request parsing */ + size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ + size_t upload_blocked_len; /* the amount written last and EGAINed */ + size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */ + uint64_t error3; /* HTTP/3 stream error code */ + curl_off_t upload_left; /* number of request bytes left to upload */ + int status_code; /* HTTP status code */ + bool resp_hds_complete; /* we have a complete, final response */ + bool closed; /* TRUE on stream close */ + bool reset; /* TRUE on stream reset */ + bool send_closed; /* stream is local closed */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ +}; + +#define H3_STREAM_CTX(d) ((struct h3_stream_ctx *)(((d) && (d)->req.p.http)? \ + ((struct HTTP *)(d)->req.p.http)->h3_ctx \ + : NULL)) +#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx +#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ + H3_STREAM_CTX(d)->id : -2) + +static CURLcode h3_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + + if(!data || !data->req.p.http) { + failf(data, "initialization failure, transfer not http initialized"); + return CURLE_FAILED_INIT; + } + + if(stream) + return CURLE_OK; + + stream = calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + stream->id = -1; + /* 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; + /* on recv, we need a flexible buffer limit since we also write + * headers to it that are not counted against the nghttp3 flow limits. */ + Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, + H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + stream->recv_buf_nonflow = 0; + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + + H3_STREAM_LCTX(data) = stream; + return CURLE_OK; +} + +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->id); + if(ctx->h3conn && !stream->closed) { + nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream->id); + nghttp3_conn_close_stream(ctx->h3conn, stream->id, + NGHTTP3_H3_REQUEST_CANCELLED); + nghttp3_conn_set_stream_user_data(ctx->h3conn, stream->id, NULL); + ngtcp2_conn_set_stream_user_data(ctx->qconn, stream->id, NULL); + stream->closed = TRUE; + } + + Curl_bufq_free(&stream->sendbuf); + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); + H3_STREAM_LCTX(data) = NULL; + } +} + +static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf, + struct Curl_easy *data, + int64_t stream_id) +{ + struct Curl_easy *sdata; + + (void)cf; + if(H3_STREAM_ID(data) == stream_id) { + return data; + } + else { + DEBUGASSERT(data->multi); + for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { + if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream_id) { + return sdata; + } + } + } + return NULL; +} + +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(stream && stream->upload_left && !stream->send_closed) + bits |= CURL_CSELECT_OUT; + if(data->state.dselect_bits != bits) { + data->state.dselect_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + +/* ngtcp2 default congestion controller does not perform pacing. Limit + the maximum packet burst to MAX_PKT_BURST packets. */ +#define MAX_PKT_BURST 10 + +struct pkt_io_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; + ngtcp2_tstamp ts; + size_t pkt_count; + ngtcp2_path_storage ps; +}; + +static void pktx_update_time(struct pkt_io_ctx *pktx, + struct Curl_cfilter *cf) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + + vquic_ctx_update_time(&ctx->q); + pktx->ts = ctx->q.last_op.tv_sec * NGTCP2_SECONDS + + ctx->q.last_op.tv_usec * NGTCP2_MICROSECONDS; +} + +static void pktx_init(struct pkt_io_ctx *pktx, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + pktx->cf = cf; + pktx->data = data; + pktx->pkt_count = 0; + ngtcp2_path_storage_zero(&pktx->ps); + pktx_update_time(pktx, cf); +} + +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx); +static CURLcode cf_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx); +static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data); + +static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) +{ + struct Curl_cfilter *cf = conn_ref->user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + return ctx->qconn; +} + +#ifdef DEBUG_NGTCP2 +static void quic_printf(void *user_data, const char *fmt, ...) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + + (void)ctx; /* TODO: need an easy handle to infof() message */ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} +#endif + +static void qlog_callback(void *user_data, uint32_t flags, + const void *data, size_t datalen) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + (void)flags; + if(ctx->qlogfd != -1) { + ssize_t rc = write(ctx->qlogfd, data, datalen); + if(rc == -1) { + /* on write error, stop further write attempts */ + close(ctx->qlogfd); + ctx->qlogfd = -1; + } + } + +} + +static void quic_settings(struct cf_ngtcp2_ctx *ctx, + struct Curl_easy *data, + struct pkt_io_ctx *pktx) +{ + ngtcp2_settings *s = &ctx->settings; + ngtcp2_transport_params *t = &ctx->transport_params; + + ngtcp2_settings_default(s); + ngtcp2_transport_params_default(t); +#ifdef DEBUG_NGTCP2 + s->log_printf = quic_printf; +#else + s->log_printf = NULL; +#endif + + (void)data; + s->initial_ts = pktx->ts; + s->handshake_timeout = QUIC_HANDSHAKE_TIMEOUT; + s->max_window = 100 * ctx->max_stream_window; + s->max_stream_window = ctx->max_stream_window; + + t->initial_max_data = 10 * ctx->max_stream_window; + t->initial_max_stream_data_bidi_local = ctx->max_stream_window; + t->initial_max_stream_data_bidi_remote = ctx->max_stream_window; + t->initial_max_stream_data_uni = ctx->max_stream_window; + t->initial_max_streams_bidi = QUIC_MAX_STREAMS; + t->initial_max_streams_uni = QUIC_MAX_STREAMS; + t->max_idle_timeout = (ctx->max_idle_ms * NGTCP2_MILLISECONDS); + if(ctx->qlogfd != -1) { + s->qlog_write = qlog_callback; + } +} + +#ifdef USE_OPENSSL +static void keylog_callback(const SSL *ssl, const char *line) +{ + (void)ssl; + Curl_tls_keylog_write_line(line); +} +#elif defined(USE_GNUTLS) +static int keylog_callback(gnutls_session_t session, const char *label, + const gnutls_datum_t *secret) +{ + gnutls_datum_t crandom; + gnutls_datum_t srandom; + + gnutls_session_get_random(session, &crandom, &srandom); + if(crandom.size != 32) { + return -1; + } + + Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); + return 0; +} +#elif defined(USE_WOLFSSL) +#if defined(HAVE_SECRET_CALLBACK) +static void keylog_callback(const WOLFSSL *ssl, const char *line) +{ + (void)ssl; + Curl_tls_keylog_write_line(line); +} +#endif +#endif + +static int init_ngh3_conn(struct Curl_cfilter *cf); + +#ifdef USE_OPENSSL +static CURLcode quic_ssl_ctx(SSL_CTX **pssl_ctx, + struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct ssl_primary_config *conn_config; + CURLcode result = CURLE_FAILED_INIT; + + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); + if(!ssl_ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) { + result = CURLE_FAILED_INIT; + goto out; + } + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); + goto out; + } +#else + if(ngtcp2_crypto_quictls_configure_client_context(ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); + goto out; + } +#endif + + SSL_CTX_set_default_verify_paths(ssl_ctx); + + { + const char *curves = conn_config->curves ? + conn_config->curves : QUIC_GROUPS; + if(!SSL_CTX_set1_curves_list(ssl_ctx, curves)) { + failf(data, "failed setting curves list for QUIC: '%s'", curves); + return CURLE_SSL_CIPHER; + } + } + +#ifndef OPENSSL_IS_BORINGSSL + { + const char *ciphers13 = conn_config->cipher_list13 ? + conn_config->cipher_list13 : QUIC_CIPHERS; + if(SSL_CTX_set_ciphersuites(ssl_ctx, ciphers13) != 1) { + failf(data, "failed setting QUIC cipher suite: %s", ciphers13); + return CURLE_SSL_CIPHER; + } + infof(data, "QUIC cipher selection: %s", ciphers13); + } +#endif + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { + SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); + } + + /* OpenSSL always tries to verify the peer, this only says whether it should + * fail to connect if the verification fails, or if it should continue + * anyway. In the latter case the result of the verification is checked with + * SSL_get_verify_result() below. */ + SSL_CTX_set_verify(ssl_ctx, conn_config->verifypeer ? + SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); + + /* give application a chance to interfere with SSL set up. */ + if(data->set.ssl.fsslctx) { + /* When a user callback is installed to modify the SSL_CTX, + * we need to do the full initialization before calling it. + * See: #11800 */ + if(!ctx->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, ssl_ctx); + if(result) + goto out; + ctx->x509_store_setup = TRUE; + } + Curl_set_in_callback(data, true); + result = (*data->set.ssl.fsslctx)(data, ssl_ctx, + data->set.ssl.fsslctxp); + Curl_set_in_callback(data, false); + if(result) { + failf(data, "error signaled by ssl ctx callback"); + goto out; + } + } + result = CURLE_OK; + +out: + *pssl_ctx = result? NULL : ssl_ctx; + if(result && ssl_ctx) + SSL_CTX_free(ssl_ctx); + return result; +} + +static CURLcode quic_set_client_cert(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + SSL_CTX *ssl_ctx = ctx->sslctx; + const struct ssl_config_data *ssl_config; + + ssl_config = Curl_ssl_cf_get_config(cf, data); + DEBUGASSERT(ssl_config); + + if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob + || ssl_config->cert_type) { + return Curl_ossl_set_client_cert( + data, ssl_ctx, ssl_config->primary.clientcert, + ssl_config->primary.cert_blob, ssl_config->cert_type, + ssl_config->key, ssl_config->key_blob, + ssl_config->key_type, ssl_config->key_passwd); + } + + return CURLE_OK; +} + +/** SSL callbacks ***/ + +static CURLcode quic_init_ssl(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + const uint8_t *alpn = NULL; + size_t alpnlen = 0; + + DEBUGASSERT(!ctx->ssl); + ctx->ssl = SSL_new(ctx->sslctx); + + SSL_set_app_data(ctx->ssl, &ctx->conn_ref); + SSL_set_connect_state(ctx->ssl); + SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); + + alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; + alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; + if(alpn) + SSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen); + + /* set SNI */ + if(ctx->peer.sni) { + if(!SSL_set_tlsext_host_name(ctx->ssl, ctx->peer.sni)) { + failf(data, "Failed set SNI"); + SSL_free(ctx->ssl); + ctx->ssl = NULL; + return CURLE_QUIC_CONNECT_ERROR; + } + } + return CURLE_OK; +} +#elif defined(USE_GNUTLS) +static CURLcode quic_init_ssl(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct ssl_primary_config *conn_config; + CURLcode result; + gnutls_datum_t alpn[2]; + /* this will need some attention when HTTPS proxy over QUIC get fixed */ + long * const pverifyresult = &data->set.ssl.certverifyresult; + int rc; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + + DEBUGASSERT(ctx->gtls == NULL); + ctx->gtls = calloc(1, sizeof(*(ctx->gtls))); + if(!ctx->gtls) + return CURLE_OUT_OF_MEMORY; + + result = gtls_client_init(data, conn_config, &data->set.ssl, + &ctx->peer, ctx->gtls, pverifyresult); + if(result) + return result; + + gnutls_session_set_ptr(ctx->gtls->session, &ctx->conn_ref); + + if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) { + CURL_TRC_CF(data, cf, + "ngtcp2_crypto_gnutls_configure_client_session failed\n"); + return CURLE_QUIC_CONNECT_ERROR; + } + + rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL); + if(rc < 0) { + CURL_TRC_CF(data, cf, "gnutls_priority_set_direct failed: %s\n", + gnutls_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; + } + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { + gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback); + } + + /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */ + alpn[0].data = (unsigned char *)H3_ALPN_H3_29 + 1; + alpn[0].size = sizeof(H3_ALPN_H3_29) - 2; + alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1; + alpn[1].size = sizeof(H3_ALPN_H3) - 2; + + gnutls_alpn_set_protocols(ctx->gtls->session, + alpn, 2, GNUTLS_ALPN_MANDATORY); + return CURLE_OK; +} +#elif defined(USE_WOLFSSL) + +static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx, + struct Curl_cfilter *cf, struct Curl_easy *data) +{ + CURLcode result = CURLE_FAILED_INIT; + struct ssl_primary_config *conn_config; + WOLFSSL_CTX *ssl_ctx = NULL; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) { + result = CURLE_FAILED_INIT; + goto out; + } + + ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if(!ssl_ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); + result = CURLE_FAILED_INIT; + goto out; + } + + wolfSSL_CTX_set_default_verify_paths(ssl_ctx); + + if(wolfSSL_CTX_set_cipher_list(ssl_ctx, conn_config->cipher_list13 ? + conn_config->cipher_list13 : + QUIC_CIPHERS) != 1) { + char error_buffer[256]; + ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); + failf(data, "wolfSSL failed to set ciphers: %s", error_buffer); + goto out; + } + + if(wolfSSL_CTX_set1_groups_list(ssl_ctx, conn_config->curves ? + conn_config->curves : + (char *)QUIC_GROUPS) != 1) { + failf(data, "wolfSSL failed to set curves"); + goto out; + } + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { +#if defined(HAVE_SECRET_CALLBACK) + wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); +#else + failf(data, "wolfSSL was built without keylog callback"); + goto out; +#endif + } + + if(conn_config->verifypeer) { + const char * const ssl_cafile = conn_config->CAfile; + const char * const ssl_capath = conn_config->CApath; + + wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + if(ssl_cafile || ssl_capath) { + /* tell wolfSSL where to find CA certificates that are used to verify + the server's certificate. */ + int rc = + wolfSSL_CTX_load_verify_locations_ex(ssl_ctx, ssl_cafile, ssl_capath, + WOLFSSL_LOAD_FLAG_IGNORE_ERR); + if(SSL_SUCCESS != rc) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate verify locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + goto out; + } + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + } +#ifdef CURL_CA_FALLBACK + else { + /* verifying the peer without any CA certificates won't work so + use wolfssl's built-in default as fallback */ + wolfSSL_CTX_set_default_verify_paths(ssl_ctx); + } +#endif + } + else { + wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); + } + + /* give application a chance to interfere with SSL set up. */ + if(data->set.ssl.fsslctx) { + Curl_set_in_callback(data, true); + result = (*data->set.ssl.fsslctx)(data, ssl_ctx, + data->set.ssl.fsslctxp); + Curl_set_in_callback(data, false); + if(result) { + failf(data, "error signaled by ssl ctx callback"); + goto out; + } + } + result = CURLE_OK; + +out: + *pssl_ctx = result? NULL : ssl_ctx; + if(result && ssl_ctx) + SSL_CTX_free(ssl_ctx); + return result; +} + +/** SSL callbacks ***/ + +static CURLcode quic_init_ssl(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + const uint8_t *alpn = NULL; + size_t alpnlen = 0; + /* this will need some attention when HTTPS proxy over QUIC get fixed */ + const char * const hostname = cf->conn->host.name; + + (void)data; + DEBUGASSERT(!ctx->ssl); + ctx->ssl = wolfSSL_new(ctx->sslctx); + + wolfSSL_set_app_data(ctx->ssl, &ctx->conn_ref); + wolfSSL_set_connect_state(ctx->ssl); + wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); + + alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; + alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; + if(alpn) + wolfSSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen); + + /* set SNI */ + wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME, + hostname, (unsigned short)strlen(hostname)); + + return CURLE_OK; +} +#endif /* defined(USE_WOLFSSL) */ + +static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data) +{ + (void)user_data; + (void)tconn; + return 0; +} + +static void report_consumed_data(struct Curl_cfilter *cf, + struct Curl_easy *data, + size_t consumed) +{ + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_ngtcp2_ctx *ctx = cf->ctx; + + if(!stream) + return; + /* the HTTP/1.1 response headers are written to the buffer, but + * consuming those does not count against flow control. */ + if(stream->recv_buf_nonflow) { + if(consumed >= stream->recv_buf_nonflow) { + consumed -= stream->recv_buf_nonflow; + stream->recv_buf_nonflow = 0; + } + else { + stream->recv_buf_nonflow -= consumed; + consumed = 0; + } + } + if(consumed > 0) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] ACK %zu bytes of DATA", + stream->id, consumed); + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, + consumed); + ngtcp2_conn_extend_max_offset(ctx->qconn, consumed); + } +} + +static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, + int64_t stream_id, uint64_t offset, + const uint8_t *buf, size_t buflen, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + nghttp3_ssize nconsumed; + int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; + struct Curl_easy *data = stream_user_data; + (void)offset; + (void)data; + + nconsumed = + nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin); + CURL_TRC_CF(data, cf, "[%" PRId64 "] read_stream(len=%zu) -> %zd", + stream_id, buflen, nconsumed); + if(nconsumed < 0) { + if(!data) { + struct Curl_easy *cdata = CF_DATA_CURRENT(cf); + CURL_TRC_CF(cdata, cf, "[%" PRId64 "] nghttp3 error on stream not " + "used by us, ignored", stream_id); + return 0; + } + ngtcp2_ccerr_set_application_error( + &ctx->last_error, + nghttp3_err_infer_quic_app_error_code((int)nconsumed), NULL, 0); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + /* number of bytes inside buflen which consists of framing overhead + * including QPACK HEADERS. In other words, it does not consume payload of + * DATA frame. */ + ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, nconsumed); + ngtcp2_conn_extend_max_offset(tconn, nconsumed); + + return 0; +} + +static int +cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id, + uint64_t offset, uint64_t datalen, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + (void)stream_id; + (void)tconn; + (void)offset; + (void)datalen; + (void)stream_user_data; + + rv = nghttp3_conn_add_ack_offset(ctx->h3conn, stream_id, datalen); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, + int64_t stream3_id, uint64_t app_error_code, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + + (void)tconn; + (void)data; + /* stream is closed... */ + + if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + + rv = nghttp3_conn_close_stream(ctx->h3conn, stream3_id, + app_error_code); + CURL_TRC_CF(data, cf, "[%" PRId64 "] quic close(err=%" + PRIu64 ") -> %d", stream3_id, app_error_code, rv); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + ngtcp2_ccerr_set_application_error( + &ctx->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +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) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + int rv; + (void)tconn; + (void)final_size; + (void)app_error_code; + (void)data; + + rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); + CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + (void)tconn; + (void)app_error_code; + (void)stream_user_data; + + rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn, + uint64_t max_streams, + void *user_data) +{ + (void)tconn; + (void)max_streams; + (void)user_data; + + return 0; +} + +static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, + uint64_t max_data, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct Curl_easy *s_data; + struct h3_stream_ctx *stream; + int rv; + (void)tconn; + (void)max_data; + (void)stream_user_data; + + rv = nghttp3_conn_unblock_stream(ctx->h3conn, stream_id); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + s_data = get_stream_easy(cf, data, stream_id); + stream = H3_STREAM_CTX(s_data); + if(stream && stream->quic_flow_blocked) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] unblock quic flow", stream_id); + stream->quic_flow_blocked = FALSE; + h3_drain_stream(cf, data); + } + return 0; +} + +static void cb_rand(uint8_t *dest, size_t destlen, + const ngtcp2_rand_ctx *rand_ctx) +{ + CURLcode result; + (void)rand_ctx; + + result = Curl_rand(NULL, dest, destlen); + if(result) { + /* cb_rand is only used for non-cryptographic context. If Curl_rand + failed, just fill 0 and call it *random*. */ + memset(dest, 0, destlen); + } +} + +static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen, + void *user_data) +{ + CURLcode result; + (void)tconn; + (void)user_data; + + result = Curl_rand(NULL, cid->data, cidlen); + if(result) + return NGTCP2_ERR_CALLBACK_FAILURE; + cid->datalen = cidlen; + + result = Curl_rand(NULL, token, NGTCP2_STATELESS_RESET_TOKENLEN); + if(result) + return NGTCP2_ERR_CALLBACK_FAILURE; + + return 0; +} + +static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_encryption_level level, + void *user_data) +{ + struct Curl_cfilter *cf = user_data; + (void)tconn; + + if(level != NGTCP2_ENCRYPTION_LEVEL_1RTT) { + return 0; + } + + if(init_ngh3_conn(cf) != CURLE_OK) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static ngtcp2_callbacks ng_callbacks = { + ngtcp2_crypto_client_initial_cb, + NULL, /* recv_client_initial */ + ngtcp2_crypto_recv_crypto_data_cb, + cb_handshake_completed, + NULL, /* recv_version_negotiation */ + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + ngtcp2_crypto_hp_mask_cb, + cb_recv_stream_data, + cb_acked_stream_data_offset, + NULL, /* stream_open */ + cb_stream_close, + NULL, /* recv_stateless_reset */ + ngtcp2_crypto_recv_retry_cb, + cb_extend_max_local_streams_bidi, + NULL, /* extend_max_local_streams_uni */ + cb_rand, + cb_get_new_connection_id, + NULL, /* remove_connection_id */ + ngtcp2_crypto_update_key_cb, /* update_key */ + NULL, /* path_validation */ + NULL, /* select_preferred_addr */ + cb_stream_reset, + NULL, /* extend_max_remote_streams_bidi */ + NULL, /* extend_max_remote_streams_uni */ + cb_extend_max_stream_data, + NULL, /* dcid_status */ + NULL, /* handshake_confirmed */ + NULL, /* recv_new_token */ + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + NULL, /* recv_datagram */ + NULL, /* ack_datagram */ + NULL, /* lost_datagram */ + ngtcp2_crypto_get_path_challenge_data_cb, + cb_stream_stop_sending, + NULL, /* version_negotiation */ + cb_recv_rx_key, + NULL, /* recv_tx_key */ + NULL, /* early_data_rejected */ +}; + +/** + * Connection maintenance like timeouts on packet ACKs etc. are done by us, not + * the OS like for TCP. POLL events on the socket therefore are not + * sufficient. + * ngtcp2 tells us when it wants to be invoked again. We handle that via + * the `Curl_expire()` mechanisms. + */ +static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct pkt_io_ctx local_pktx; + ngtcp2_tstamp expiry; + + if(!pktx) { + pktx_init(&local_pktx, cf, data); + pktx = &local_pktx; + } + else { + pktx_update_time(pktx, cf); + } + + expiry = ngtcp2_conn_get_expiry(ctx->qconn); + if(expiry != UINT64_MAX) { + if(expiry <= pktx->ts) { + CURLcode result; + int rv = ngtcp2_conn_handle_expiry(ctx->qconn, pktx->ts); + if(rv) { + failf(data, "ngtcp2_conn_handle_expiry returned error: %s", + ngtcp2_strerror(rv)); + ngtcp2_ccerr_set_liberr(&ctx->last_error, rv, NULL, 0); + return CURLE_SEND_ERROR; + } + result = cf_progress_ingress(cf, data, pktx); + if(result) + return result; + result = cf_progress_egress(cf, data, pktx); + if(result) + return result; + /* ask again, things might have changed */ + expiry = ngtcp2_conn_get_expiry(ctx->qconn); + } + + if(expiry > pktx->ts) { + ngtcp2_duration timeout = expiry - pktx->ts; + if(timeout % NGTCP2_MILLISECONDS) { + timeout += NGTCP2_MILLISECONDS; + } + Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC); + } + } + return CURLE_OK; +} + +static void cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + bool want_recv = CURL_WANT_RECV(data); + bool want_send = CURL_WANT_SEND(data); + + if(ctx->qconn && (want_recv || want_send)) { + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_call_data save; + bool c_exhaust, s_exhaust; + + CF_DATA_SAVE(save, cf, data); + c_exhaust = !ngtcp2_conn_get_cwnd_left(ctx->qconn) || + !ngtcp2_conn_get_max_data_left(ctx->qconn); + s_exhaust = stream && stream->id >= 0 && stream->quic_flow_blocked; + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + !Curl_bufq_is_empty(&ctx->q.sendbuf); + + Curl_pollset_set(data, ps, ctx->q.sockfd, want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } +} + +static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + (void)conn; + (void)stream_id; + + /* we might be called by nghttp3 after we already cleaned up */ + if(!stream) + return 0; + + stream->closed = TRUE; + stream->error3 = app_error_code; + if(stream->error3 != NGHTTP3_H3_NO_ERROR) { + stream->reset = TRUE; + stream->send_closed = TRUE; + CURL_TRC_CF(data, cf, "[%" PRId64 "] RESET: error %" PRId64, + stream->id, stream->error3); + } + else { + CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", stream->id); + } + h3_drain_stream(cf, data); + return 0; +} + +/* + * write_resp_raw() copies response data in raw format to the `data`'s + * receive buffer. If not enough space is available, it appends to the + * `data`'s overflow buffer. + */ +static CURLcode write_resp_raw(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, size_t memlen, + bool flow) +{ + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result = CURLE_OK; + ssize_t nwritten; + + (void)cf; + if(!stream) { + return CURLE_RECV_ERROR; + } + nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result); + if(nwritten < 0) { + return result; + } + + if(!flow) + stream->recv_buf_nonflow += (size_t)nwritten; + + if((size_t)nwritten < memlen) { + /* This MUST not happen. Our recbuf is dimensioned to hold the + * full max_stream_window and then some for this very reason. */ + DEBUGASSERT(0); + return CURLE_RECV_ERROR; + } + return result; +} + +static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, + const uint8_t *buf, size_t buflen, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result; + + (void)conn; + (void)stream3_id; + + if(!stream) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + result = write_resp_raw(cf, data, buf, buflen, TRUE); + if(result) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, ERROR receiving %d", + stream->id, buflen, result); + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu", stream->id, buflen); + h3_drain_stream(cf, data); + return 0; +} + +static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id, + size_t consumed, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + (void)conn; + (void)stream_user_data; + + /* nghttp3 has consumed bytes on the QUIC stream and we need to + * tell the QUIC connection to increase its flow control */ + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream3_id, consumed); + ngtcp2_conn_extend_max_offset(ctx->qconn, consumed); + return 0; +} + +static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, + int fin, void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result = CURLE_OK; + (void)conn; + (void)stream_id; + (void)fin; + (void)cf; + + if(!stream) + return 0; + /* add a CRLF only if we've received some headers */ + result = write_resp_raw(cf, data, "\r\n", 2, FALSE); + if(result) { + return -1; + } + + CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers, status=%d", + stream_id, stream->status_code); + if(stream->status_code / 100 != 1) { + stream->resp_hds_complete = TRUE; + } + h3_drain_stream(cf, data); + return 0; +} + +static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, + int32_t token, nghttp3_rcbuf *name, + nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); + nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result = CURLE_OK; + (void)conn; + (void)stream_id; + (void)token; + (void)flags; + (void)cf; + + /* we might have cleaned up this transfer already */ + if(!stream) + return 0; + + if(token == NGHTTP3_QPACK_TOKEN__STATUS) { + char line[14]; /* status line is always 13 characters long */ + size_t ncopy; + + result = Curl_http_decode_status(&stream->status_code, + (const char *)h3val.base, h3val.len); + if(result) + return -1; + ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", + stream->status_code); + CURL_TRC_CF(data, cf, "[%" PRId64 "] status: %s", stream_id, line); + result = write_resp_raw(cf, data, line, ncopy, FALSE); + if(result) { + return -1; + } + } + else { + /* 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); + result = write_resp_raw(cf, data, h3name.base, h3name.len, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, ": ", 2, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, h3val.base, h3val.len, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, "\r\n", 2, FALSE); + if(result) { + return -1; + } + } + return 0; +} + +static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + (void)conn; + (void)stream_user_data; + + rv = ngtcp2_conn_shutdown_stream_read(ctx->qconn, 0, stream_id, + app_error_code); + if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + int rv; + (void)conn; + (void)data; + + rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, 0, stream_id, + app_error_code); + CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static nghttp3_callbacks ngh3_callbacks = { + cb_h3_acked_req_body, /* acked_stream_data */ + cb_h3_stream_close, + cb_h3_recv_data, + cb_h3_deferred_consume, + NULL, /* begin_headers */ + cb_h3_recv_header, + cb_h3_end_headers, + NULL, /* begin_trailers */ + cb_h3_recv_header, + NULL, /* end_trailers */ + cb_h3_stop_sending, + NULL, /* end_stream */ + cb_h3_reset_stream, + NULL, /* shutdown */ + NULL /* recv_settings */ +}; + +static int init_ngh3_conn(struct Curl_cfilter *cf) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + CURLcode result; + int rc; + int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id; + + if(ngtcp2_conn_get_streams_uni_left(ctx->qconn) < 3) { + return CURLE_QUIC_CONNECT_ERROR; + } + + nghttp3_settings_default(&ctx->h3settings); + + rc = nghttp3_conn_client_new(&ctx->h3conn, + &ngh3_callbacks, + &ctx->h3settings, + nghttp3_mem_default(), + cf); + if(rc) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + + rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto fail; + } + + rc = nghttp3_conn_bind_control_stream(ctx->h3conn, ctrl_stream_id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto fail; + } + + rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_enc_stream_id, NULL); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto fail; + } + + rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_dec_stream_id, NULL); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto fail; + } + + rc = nghttp3_conn_bind_qpack_streams(ctx->h3conn, qpack_enc_stream_id, + qpack_dec_stream_id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto fail; + } + + return CURLE_OK; +fail: + + return result; +} + +static ssize_t recv_closed_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + CURLcode *err) +{ + ssize_t nread = -1; + + (void)cf; + if(stream->reset) { + failf(data, + "HTTP/3 stream %" PRId64 " reset by server", stream->id); + *err = stream->resp_hds_complete? CURLE_PARTIAL_FILE : CURLE_HTTP3; + goto out; + } + else if(!stream->resp_hds_complete) { + failf(data, + "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" + " all response header fields, treated as error", + stream->id); + *err = CURLE_HTTP3; + goto out; + } + *err = CURLE_OK; + nread = 0; + +out: + return nread; +} + +/* incoming data frames on the h3 stream */ +static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + ssize_t nread = -1; + struct cf_call_data save; + struct pkt_io_ctx pktx; + + (void)ctx; + + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx); + DEBUGASSERT(ctx->qconn); + DEBUGASSERT(ctx->h3conn); + *err = CURLE_OK; + + pktx_init(&pktx, cf, data); + + if(!stream) { + *err = CURLE_RECV_ERROR; + goto out; + } + + if(!Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + if(nread < 0) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->id, len, nread, *err); + goto out; + } + report_consumed_data(cf, data, nread); + } + + if(cf_progress_ingress(cf, data, &pktx)) { + *err = CURLE_RECV_ERROR; + nread = -1; + goto out; + } + + /* recvbuf had nothing before, maybe after progressing ingress? */ + if(nread < 0 && !Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + if(nread < 0) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->id, len, nread, *err); + goto out; + } + report_consumed_data(cf, data, nread); + } + + if(nread > 0) { + h3_drain_stream(cf, data); + } + else { + if(stream->closed) { + nread = recv_closed_stream(cf, data, stream, err); + goto out; + } + *err = CURLE_AGAIN; + nread = -1; + } + +out: + if(cf_progress_egress(cf, data, &pktx)) { + *err = CURLE_SEND_ERROR; + nread = -1; + } + else { + CURLcode result2 = check_and_set_expiry(cf, data, &pktx); + if(result2) { + *err = result2; + nread = -1; + } + } + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(len=%zu) -> %zd, %d", + stream? stream->id : -1, len, nread, *err); + CF_DATA_RESTORE(cf, save); + return nread; +} + +static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + size_t skiplen; + + (void)cf; + if(!stream) + return 0; + /* 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; + else + skiplen = (size_t)datalen; + Curl_bufq_skip(&stream->sendbuf, skiplen); + stream->sendbuf_len_in_flight -= skiplen; + + /* Everything ACKed, we resume upload processing */ + if(!stream->sendbuf_len_in_flight) { + int rv = nghttp3_conn_resume_stream(conn, stream_id); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static nghttp3_ssize +cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, + nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + ssize_t nwritten = 0; + size_t nvecs = 0; + (void)cf; + (void)conn; + (void)stream_id; + (void)user_data; + (void)veccnt; + + if(!stream) + 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` + * 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 + * to nghttp3. Do that now, if we can. */ + if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + nvecs = 0; + while(nvecs < veccnt && + Curl_bufq_peek_at(&stream->sendbuf, + stream->sendbuf_len_in_flight, + (const unsigned char **)&vec[nvecs].base, + &vec[nvecs].len)) { + stream->sendbuf_len_in_flight += vec[nvecs].len; + nwritten += vec[nvecs].len; + ++nvecs; + } + DEBUGASSERT(nvecs > 0); /* we SHOULD have been be able to peek */ + } + + if(nwritten > 0 && stream->upload_left != -1) + stream->upload_left -= nwritten; + + /* When we stopped sending and everything in `sendbuf` is "in flight", + * we are at the end of the request body. */ + if(stream->upload_left == 0) { + *pflags = NGHTTP3_DATA_FLAG_EOF; + stream->send_closed = TRUE; + } + else if(!nwritten) { + /* Not EOF, and nothing to give, we signal WOULDBLOCK. */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> AGAIN", + stream->id); + return NGHTTP3_ERR_WOULDBLOCK; + } + + CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> " + "%d vecs%s with %zu (buffered=%zu, left=%" + CURL_FORMAT_CURL_OFF_T ")", + stream->id, (int)nvecs, + *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"", + nwritten, Curl_bufq_len(&stream->sendbuf), + stream->upload_left); + return (nghttp3_ssize)nvecs; +} + +/* Index where :authority header field will appear in request header + field list. */ +#define AUTHORITY_DST_IDX 3 + +static ssize_t h3_stream_open(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t len, + CURLcode *err) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = NULL; + struct dynhds h2_headers; + size_t nheader; + nghttp3_nv *nva = NULL; + int rc = 0; + unsigned int i; + ssize_t nwritten = -1; + nghttp3_data_reader reader; + nghttp3_data_reader *preader = NULL; + + Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + + *err = h3_data_setup(cf, data); + if(*err) + goto out; + stream = H3_STREAM_CTX(data); + DEBUGASSERT(stream); + if(!stream) { + *err = CURLE_FAILED_INIT; + goto out; + } + + nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); + if(nwritten < 0) + goto out; + if(!stream->h1.done) { + /* need more data */ + goto out; + } + DEBUGASSERT(stream->h1.req); + + *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); + if(*err) { + nwritten = -1; + goto out; + } + /* no longer needed */ + Curl_h1_req_parse_free(&stream->h1); + + nheader = Curl_dynhds_count(&h2_headers); + nva = malloc(sizeof(nghttp3_nv) * nheader); + if(!nva) { + *err = CURLE_OUT_OF_MEMORY; + nwritten = -1; + goto out; + } + + for(i = 0; i < nheader; ++i) { + struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); + nva[i].name = (unsigned char *)e->name; + nva[i].namelen = e->namelen; + nva[i].value = (unsigned char *)e->value; + nva[i].valuelen = e->valuelen; + nva[i].flags = NGHTTP3_NV_FLAG_NONE; + } + + rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &stream->id, data); + if(rc) { + failf(data, "can get bidi streams"); + *err = CURLE_SEND_ERROR; + goto out; + } + + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + case HTTPREQ_PUT: + /* known request body size or -1 */ + if(data->state.infilesize != -1) + stream->upload_left = data->state.infilesize; + else + /* data sending without specifying the data amount up front */ + stream->upload_left = -1; /* unknown */ + break; + default: + /* there is not request body */ + stream->upload_left = 0; /* no request body */ + break; + } + + stream->send_closed = (stream->upload_left == 0); + if(!stream->send_closed) { + reader.read_data = cb_h3_read_req_body; + preader = &reader; + } + + rc = nghttp3_conn_submit_request(ctx->h3conn, stream->id, + nva, nheader, preader, data); + if(rc) { + switch(rc) { + case NGHTTP3_ERR_CONN_CLOSING: + CURL_TRC_CF(data, cf, "h3sid[%"PRId64"] failed to send, " + "connection is closing", stream->id); + break; + default: + CURL_TRC_CF(data, cf, "h3sid[%"PRId64"] failed to send -> %d (%s)", + stream->id, rc, ngtcp2_strerror(rc)); + break; + } + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + + if(Curl_trc_is_verbose(data)) { + infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s", + stream->id, data->state.url); + for(i = 0; i < nheader; ++i) { + infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", stream->id, + (int)nva[i].namelen, nva[i].name, + (int)nva[i].valuelen, nva[i].value); + } + } + +out: + free(nva); + Curl_dynhds_free(&h2_headers); + return nwritten; +} + +static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + ssize_t sent = 0; + struct cf_call_data save; + struct pkt_io_ctx pktx; + CURLcode result; + + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx->qconn); + DEBUGASSERT(ctx->h3conn); + pktx_init(&pktx, cf, data); + *err = CURLE_OK; + + result = cf_progress_ingress(cf, data, &pktx); + if(result) { + *err = result; + sent = -1; + } + + if(!stream || stream->id < 0) { + sent = h3_stream_open(cf, data, buf, len, err); + if(sent < 0) { + CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err); + goto out; + } + stream = H3_STREAM_CTX(data); + } + else if(stream->upload_blocked_len) { + /* the data in `buf` has already been submitted or added to the + * buffers, but have been EAGAINed on the last invocation. */ + DEBUGASSERT(len >= stream->upload_blocked_len); + if(len < stream->upload_blocked_len) { + /* Did we get called again with a smaller `len`? This should not + * happen. We are not prepared to handle that. */ + failf(data, "HTTP/3 send again with decreased length"); + *err = CURLE_HTTP3; + sent = -1; + goto out; + } + sent = (ssize_t)stream->upload_blocked_len; + stream->upload_blocked_len = 0; + } + else if(stream->closed) { + if(stream->resp_hds_complete) { + /* Server decided to close the stream after having sent us a final + * response. This is valid if it is not interested in the request + * body. This happens on 30x or 40x responses. + * We silently discard the data sent, since this is not a transport + * error situation. */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data" + "on closed stream with response", stream->id); + *err = CURLE_OK; + sent = (ssize_t)len; + goto out; + } + *err = CURLE_HTTP3; + sent = -1; + goto out; + } + else { + sent = Curl_bufq_write(&stream->sendbuf, buf, len, err); + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to " + "sendbuf(len=%zu) -> %zd, %d", + stream->id, len, sent, *err); + if(sent < 0) { + goto out; + } + + (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id); + } + + result = cf_progress_egress(cf, data, &pktx); + if(result) { + *err = result; + sent = -1; + } + + if(stream && sent > 0 && stream->sendbuf_len_in_flight) { + /* We have unacknowledged DATA and cannot report success to our + * caller. Instead we EAGAIN and remember how much we have already + * "written" into our various internal connection buffers. */ + stream->upload_blocked_len = sent; + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu), " + "%zu bytes in flight -> EGAIN", stream->id, len, + stream->sendbuf_len_in_flight); + *err = CURLE_AGAIN; + sent = -1; + } + +out: + result = check_and_set_expiry(cf, data, &pktx); + if(result) { + *err = result; + sent = -1; + } + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %zd, %d", + stream? stream->id : -1, len, sent, *err); + CF_DATA_RESTORE(cf, save); + return sent; +} + +static CURLcode qng_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct ssl_primary_config *conn_config; + CURLcode result = CURLE_OK; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; + + if(conn_config->verifyhost) { +#ifdef USE_OPENSSL + X509 *server_cert; + server_cert = SSL_get1_peer_certificate(ctx->ssl); + if(!server_cert) { + return CURLE_PEER_FAILED_VERIFICATION; + } + result = Curl_ossl_verifyhost(data, cf->conn, &ctx->peer, server_cert); + X509_free(server_cert); + if(result) + return result; +#elif defined(USE_GNUTLS) + result = Curl_gtls_verifyserver(data, ctx->gtls->session, + conn_config, &data->set.ssl, &ctx->peer, + data->set.str[STRING_SSL_PINNEDPUBLICKEY]); + if(result) + return result; +#elif defined(USE_WOLFSSL) + if(!ctx->peer.sni || + wolfSSL_check_domain_name(ctx->ssl, ctx->peer.sni) == SSL_FAILURE) + return CURLE_PEER_FAILED_VERIFICATION; +#endif + infof(data, "Verified certificate just fine"); + } + else + infof(data, "Skipped certificate verification"); +#ifdef USE_OPENSSL + if(data->set.ssl.certinfo) + /* asked to gather certificate info */ + (void)Curl_ossl_certchain(data, ctx->ssl); +#endif + return result; +} + +static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, + struct sockaddr_storage *remote_addr, + socklen_t remote_addrlen, int ecn, + void *userp) +{ + struct pkt_io_ctx *pktx = userp; + struct cf_ngtcp2_ctx *ctx = pktx->cf->ctx; + ngtcp2_pkt_info pi; + ngtcp2_path path; + int rv; + + ++pktx->pkt_count; + ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen); + ngtcp2_addr_init(&path.remote, (struct sockaddr *)remote_addr, + remote_addrlen); + pi.ecn = (uint8_t)ecn; + + rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, pkt, pktlen, pktx->ts); + if(rv) { + CURL_TRC_CF(pktx->data, pktx->cf, "ingress, read_pkt -> %s (%d)", + ngtcp2_strerror(rv), rv); + if(!ctx->last_error.error_code) { + if(rv == NGTCP2_ERR_CRYPTO) { + ngtcp2_ccerr_set_tls_alert(&ctx->last_error, + ngtcp2_conn_get_tls_alert(ctx->qconn), + NULL, 0); + } + else { + ngtcp2_ccerr_set_liberr(&ctx->last_error, rv, NULL, 0); + } + } + + if(rv == NGTCP2_ERR_CRYPTO) + /* this is a "TLS problem", but a failed certificate verification + is a common reason for this */ + return CURLE_PEER_FAILED_VERIFICATION; + return CURLE_RECV_ERROR; + } + + return CURLE_OK; +} + +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct pkt_io_ctx local_pktx; + size_t pkts_chunk = 128, i; + size_t pkts_max = 10 * pkts_chunk; + CURLcode result = CURLE_OK; + + if(!pktx) { + pktx_init(&local_pktx, cf, data); + pktx = &local_pktx; + } + else { + pktx_update_time(pktx, cf); + } + +#ifdef USE_OPENSSL + if(!ctx->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, ctx->sslctx); + if(result) + return result; + ctx->x509_store_setup = TRUE; + } +#endif + + for(i = 0; i < pkts_max; i += pkts_chunk) { + pktx->pkt_count = 0; + result = vquic_recv_packets(cf, data, &ctx->q, pkts_chunk, + recv_pkt, pktx); + if(result) /* error */ + break; + if(pktx->pkt_count < pkts_chunk) /* got less than we could */ + break; + /* give egress a chance before we receive more */ + result = cf_progress_egress(cf, data, pktx); + if(result) /* error */ + break; + } + return result; +} + +/** + * Read a network packet to send from ngtcp2 into `buf`. + * Return number of bytes written or -1 with *err set. + */ +static ssize_t read_pkt_to_send(void *userp, + unsigned char *buf, size_t buflen, + CURLcode *err) +{ + struct pkt_io_ctx *x = userp; + struct cf_ngtcp2_ctx *ctx = x->cf->ctx; + nghttp3_vec vec[16]; + nghttp3_ssize veccnt; + ngtcp2_ssize ndatalen; + uint32_t flags; + int64_t stream_id; + int fin; + ssize_t nwritten, n; + veccnt = 0; + stream_id = -1; + fin = 0; + + /* ngtcp2 may want to put several frames from different streams into + * this packet. `NGTCP2_WRITE_STREAM_FLAG_MORE` tells it to do so. + * When `NGTCP2_ERR_WRITE_MORE` is returned, we *need* to make + * another iteration. + * When ngtcp2 is happy (because it has no other frame that would fit + * or it has nothing more to send), it returns the total length + * of the assembled packet. This may be 0 if there was nothing to send. */ + nwritten = 0; + *err = CURLE_OK; + for(;;) { + + if(ctx->h3conn && ngtcp2_conn_get_max_data_left(ctx->qconn)) { + veccnt = nghttp3_conn_writev_stream(ctx->h3conn, &stream_id, &fin, vec, + sizeof(vec) / sizeof(vec[0])); + if(veccnt < 0) { + failf(x->data, "nghttp3_conn_writev_stream returned error: %s", + nghttp3_strerror((int)veccnt)); + ngtcp2_ccerr_set_application_error( + &ctx->last_error, + nghttp3_err_infer_quic_app_error_code((int)veccnt), NULL, 0); + *err = CURLE_SEND_ERROR; + return -1; + } + } + + flags = NGTCP2_WRITE_STREAM_FLAG_MORE | + (fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0); + n = ngtcp2_conn_writev_stream(ctx->qconn, &x->ps.path, + NULL, buf, buflen, + &ndatalen, flags, stream_id, + (const ngtcp2_vec *)vec, veccnt, x->ts); + if(n == 0) { + /* nothing to send */ + *err = CURLE_AGAIN; + nwritten = -1; + goto out; + } + else if(n < 0) { + switch(n) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(x->data); + DEBUGASSERT(ndatalen == -1); + nghttp3_conn_block_stream(ctx->h3conn, stream_id); + CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] block quic flow", + stream_id); + DEBUGASSERT(stream); + if(stream) + stream->quic_flow_blocked = TRUE; + n = 0; + break; + } + case NGTCP2_ERR_STREAM_SHUT_WR: + DEBUGASSERT(ndatalen == -1); + nghttp3_conn_shutdown_stream_write(ctx->h3conn, stream_id); + n = 0; + break; + case NGTCP2_ERR_WRITE_MORE: + /* ngtcp2 wants to send more. update the flow of the stream whose data + * is in the buffer and continue */ + DEBUGASSERT(ndatalen >= 0); + n = 0; + break; + default: + DEBUGASSERT(ndatalen == -1); + failf(x->data, "ngtcp2_conn_writev_stream returned error: %s", + ngtcp2_strerror((int)n)); + ngtcp2_ccerr_set_liberr(&ctx->last_error, (int)n, NULL, 0); + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + } + + if(ndatalen >= 0) { + /* we add the amount of data bytes to the flow windows */ + int rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen); + if(rv) { + failf(x->data, "nghttp3_conn_add_write_offset returned error: %s\n", + nghttp3_strerror(rv)); + return CURLE_SEND_ERROR; + } + } + + if(n > 0) { + /* packet assembled, leave */ + nwritten = n; + goto out; + } + } +out: + return nwritten; +} + +static CURLcode cf_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + ssize_t nread; + size_t max_payload_size, path_max_payload_size, max_pktcnt; + size_t pktcnt = 0; + size_t gsolen = 0; /* this disables gso until we have a clue */ + CURLcode curlcode; + struct pkt_io_ctx local_pktx; + + if(!pktx) { + pktx_init(&local_pktx, cf, data); + pktx = &local_pktx; + } + else { + pktx_update_time(pktx, cf); + ngtcp2_path_storage_zero(&pktx->ps); + } + + curlcode = vquic_flush(cf, data, &ctx->q); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + + /* In UDP, there is a maximum theoretical packet paload length and + * a minimum payload length that is "guarantueed" to work. + * To detect if this minimum payload can be increased, ngtcp2 sends + * now and then a packet payload larger than the minimum. It that + * is ACKed by the peer, both parties know that it works and + * the subsequent packets can use a larger one. + * This is called PMTUD (Path Maximum Transmission Unit Discovery). + * Since a PMTUD might be rejected right on send, we do not want it + * be followed by other packets of lesser size. Because those would + * also fail then. So, if we detect a PMTUD while buffering, we flush. + */ + max_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(ctx->qconn); + path_max_payload_size = + ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn); + /* maximum number of packets buffered before we flush to the socket */ + max_pktcnt = CURLMIN(MAX_PKT_BURST, + ctx->q.sendbuf.chunk_size / max_payload_size); + + for(;;) { + /* add the next packet to send, if any, to our buffer */ + nread = Curl_bufq_sipn(&ctx->q.sendbuf, max_payload_size, + read_pkt_to_send, pktx, &curlcode); + if(nread < 0) { + if(curlcode != CURLE_AGAIN) + return curlcode; + /* Nothing more to add, flush and leave */ + curlcode = vquic_send(cf, data, &ctx->q, gsolen); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + goto out; + } + + DEBUGASSERT(nread > 0); + if(pktcnt == 0) { + /* first packet in buffer. This is either of a known, "good" + * payload size or it is a PMTUD. We'll see. */ + gsolen = (size_t)nread; + } + else if((size_t)nread > gsolen || + (gsolen > path_max_payload_size && (size_t)nread != gsolen)) { + /* The just added packet is a PMTUD *or* the one(s) before the + * just added were PMTUD and the last one is smaller. + * Flush the buffer before the last add. */ + curlcode = vquic_send_tail_split(cf, data, &ctx->q, + gsolen, nread, nread); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + pktcnt = 0; + continue; + } + + if(++pktcnt >= max_pktcnt || (size_t)nread < gsolen) { + /* Reached MAX_PKT_BURST *or* + * the capacity of our buffer *or* + * last add was shorter than the previous ones, flush */ + curlcode = vquic_send(cf, data, &ctx->q, gsolen); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + /* pktbuf has been completely sent */ + pktcnt = 0; + } + } + +out: + return CURLE_OK; +} + +/* + * Called from transfer.c:data_pending to know if we should keep looping + * to receive more data from the connection. + */ +static bool cf_ngtcp2_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + const struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + (void)cf; + return stream && !Curl_bufq_is_empty(&stream->recvbuf); +} + +static CURLcode h3_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ + /* TODO: there seems right now no API in ngtcp2 to shrink/enlarge + * the streams windows. As we do in HTTP/2. */ + if(!pause) { + h3_drain_stream(cf, data); + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + return CURLE_OK; +} + +static CURLcode cf_ngtcp2_data_event(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; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_DATA_SETUP: + break; + case CF_CTRL_DATA_PAUSE: + result = h3_data_pause(cf, data, (arg1 != 0)); + break; + case CF_CTRL_DATA_DETACH: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE_SEND: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + if(stream && !stream->send_closed) { + stream->send_closed = TRUE; + stream->upload_left = Curl_bufq_len(&stream->sendbuf); + (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id); + } + break; + } + case CF_CTRL_DATA_IDLE: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + CURL_TRC_CF(data, cf, "data idle"); + if(stream && !stream->closed) { + result = check_and_set_expiry(cf, data, NULL); + if(result) + CURL_TRC_CF(data, cf, "data idle, check_and_set_expiry -> %d", result); + } + break; + } + default: + break; + } + CF_DATA_RESTORE(cf, save); + return result; +} + +static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx) +{ + struct cf_call_data save = ctx->call_data; + + if(ctx->qlogfd != -1) { + close(ctx->qlogfd); + } +#ifdef USE_OPENSSL + if(ctx->ssl) + SSL_free(ctx->ssl); + if(ctx->sslctx) + SSL_CTX_free(ctx->sslctx); +#elif defined(USE_GNUTLS) + if(ctx->gtls) { + if(ctx->gtls->cred) + gnutls_certificate_free_credentials(ctx->gtls->cred); + if(ctx->gtls->session) + gnutls_deinit(ctx->gtls->session); + free(ctx->gtls); + } +#elif defined(USE_WOLFSSL) + if(ctx->ssl) + wolfSSL_free(ctx->ssl); + if(ctx->sslctx) + wolfSSL_CTX_free(ctx->sslctx); +#endif + vquic_ctx_free(&ctx->q); + if(ctx->h3conn) + nghttp3_conn_del(ctx->h3conn); + if(ctx->qconn) + ngtcp2_conn_del(ctx->qconn); + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_ssl_peer_cleanup(&ctx->peer); + + memset(ctx, 0, sizeof(*ctx)); + ctx->qlogfd = -1; + ctx->call_data = save; +} + +static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + if(ctx && ctx->qconn) { + char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; + struct pkt_io_ctx pktx; + ngtcp2_ssize rc; + + CURL_TRC_CF(data, cf, "close"); + pktx_init(&pktx, cf, data); + rc = ngtcp2_conn_write_connection_close(ctx->qconn, NULL, /* path */ + NULL, /* pkt_info */ + (uint8_t *)buffer, sizeof(buffer), + &ctx->last_error, pktx.ts); + if(rc > 0) { + while((send(ctx->q.sockfd, buffer, (SEND_TYPE_ARG3)rc, 0) == -1) && + SOCKERRNO == EINTR); + } + + cf_ngtcp2_ctx_clear(ctx); + } + + cf->connected = FALSE; + CF_DATA_RESTORE(cf, save); +} + +static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + CURL_TRC_CF(data, cf, "destroy"); + if(ctx) { + cf_ngtcp2_ctx_clear(ctx); + free(ctx); + } + cf->ctx = NULL; + /* No CF_DATA_RESTORE(cf, save) possible */ + (void)save; +} + +/* + * Might be called twice for happy eyeballs. + */ +static CURLcode cf_connect_start(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rc; + int rv; + CURLcode result; + const struct Curl_sockaddr_ex *sockaddr = NULL; + int qfd; + + ctx->version = NGTCP2_PROTO_VER_MAX; + ctx->max_stream_window = H3_STREAM_WINDOW_SIZE; + ctx->max_idle_ms = CURL_QUIC_MAX_IDLE_MS; + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + + result = Curl_ssl_peer_init(&ctx->peer, cf); + if(result) + return result; + +#ifdef USE_OPENSSL + result = quic_ssl_ctx(&ctx->sslctx, cf, data); + if(result) + return result; + + result = quic_set_client_cert(cf, data); + if(result) + return result; +#elif defined(USE_WOLFSSL) + result = quic_ssl_ctx(&ctx->sslctx, cf, data); + if(result) + return result; +#endif + + result = quic_init_ssl(cf, data); + if(result) + return result; + + ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; + result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN); + if(result) + return result; + + ctx->scid.datalen = NGTCP2_MAX_CIDLEN; + result = Curl_rand(data, ctx->scid.data, NGTCP2_MAX_CIDLEN); + if(result) + return result; + + (void)Curl_qlogdir(data, ctx->scid.data, NGTCP2_MAX_CIDLEN, &qfd); + ctx->qlogfd = qfd; /* -1 if failure above */ + quic_settings(ctx, data, pktx); + + result = vquic_ctx_init(&ctx->q); + if(result) + return result; + + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, + &sockaddr, NULL, NULL, NULL, NULL); + if(!sockaddr) + return CURLE_QUIC_CONNECT_ERROR; + ctx->q.local_addrlen = sizeof(ctx->q.local_addr); + rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, + &ctx->q.local_addrlen); + if(rv == -1) + return CURLE_QUIC_CONNECT_ERROR; + + ngtcp2_addr_init(&ctx->connected_path.local, + (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen); + ngtcp2_addr_init(&ctx->connected_path.remote, + &sockaddr->sa_addr, 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, + NULL, cf); + if(rc) + return CURLE_QUIC_CONNECT_ERROR; + +#ifdef USE_GNUTLS + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->gtls->session); +#else + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ssl); +#endif + + ngtcp2_ccerr_default(&ctx->last_error); + + ctx->conn_ref.get_conn = get_conn; + ctx->conn_ref.user_data = cf; + + return CURLE_OK; +} + +static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + struct curltime now; + struct pkt_io_ctx pktx; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* Connect the UDP filter first */ + if(!cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + } + + *done = FALSE; + now = Curl_now(); + pktx_init(&pktx, cf, data); + + CF_DATA_SAVE(save, cf, data); + + if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) { + /* Not time yet to attempt the next connect */ + CURL_TRC_CF(data, cf, "waiting for reconnect time"); + goto out; + } + + if(!ctx->qconn) { + ctx->started_at = now; + result = cf_connect_start(cf, data, &pktx); + if(result) + goto out; + result = cf_progress_egress(cf, data, &pktx); + /* we do not expect to be able to recv anything yet */ + goto out; + } + + result = cf_progress_ingress(cf, data, &pktx); + if(result) + goto out; + + result = cf_progress_egress(cf, data, &pktx); + if(result) + goto out; + + if(ngtcp2_conn_get_handshake_completed(ctx->qconn)) { + ctx->handshake_at = now; + CURL_TRC_CF(data, cf, "handshake complete after %dms", + (int)Curl_timediff(now, ctx->started_at)); + result = qng_verify_peer(cf, data); + if(!result) { + CURL_TRC_CF(data, cf, "peer verified"); + cf->connected = TRUE; + cf->conn->alpn = CURL_HTTP_VERSION_3; + *done = TRUE; + connkeep(cf->conn, "HTTP/3 default"); + } + } + +out: + if(result == CURLE_RECV_ERROR && ctx->qconn && + ngtcp2_conn_in_draining_period(ctx->qconn)) { + /* When a QUIC server instance is shutting down, it may send us a + * CONNECTION_CLOSE right away. Our connection then enters the DRAINING + * state. The CONNECT may work in the near future again. Indicate + * that as a "weird" reply. */ + result = CURLE_WEIRD_SERVER_REPLY; + } + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(result) { + const char *r_ip = NULL; + int r_port = 0; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, + &r_ip, &r_port, NULL, NULL); + infof(data, "QUIC connect to %s port %u failed: %s", + r_ip, r_port, curl_easy_strerror(result)); + } +#endif + if(!result && ctx->qconn) { + result = check_and_set_expiry(cf, data, &pktx); + } + if(result || *done) + CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); + CF_DATA_RESTORE(cf, save); + return result; +} + +static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_call_data save; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: { + const ngtcp2_transport_params *rp; + DEBUGASSERT(pres1); + + CF_DATA_SAVE(save, cf, data); + rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); + if(rp) + *pres1 = (rp->initial_max_streams_bidi > INT_MAX)? + INT_MAX : (int)rp->initial_max_streams_bidi; + else /* not arrived yet? */ + *pres1 = Curl_multi_max_concurrent_streams(data->multi); + CURL_TRC_CF(data, cf, "query max_conncurrent -> %d", *pres1); + CF_DATA_RESTORE(cf, save); + return CURLE_OK; + } + case CF_QUERY_CONNECT_REPLY_MS: + if(ctx->got_first_byte) { + timediff_t ms = Curl_timediff(ctx->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->got_first_byte) + *when = ctx->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; + } + default: + break; + } + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + bool alive = FALSE; + const ngtcp2_transport_params *rp; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + *input_pending = FALSE; + if(!ctx->qconn) + goto out; + + /* Both sides of the QUIC connection announce they max idle times in + * the transport parameters. Look at the minimum of both and if + * we exceed this, regard the connection as dead. The other side + * may have completely purged it and will no longer respond + * to any packets from us. */ + rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); + if(rp) { + timediff_t idletime; + uint64_t idle_ms = ctx->max_idle_ms; + + if(rp->max_idle_timeout && + (rp->max_idle_timeout / NGTCP2_MILLISECONDS) < idle_ms) + idle_ms = (rp->max_idle_timeout / NGTCP2_MILLISECONDS); + idletime = Curl_timediff(Curl_now(), ctx->q.last_io); + if(idletime > 0 && (uint64_t)idletime > idle_ms) + goto out; + } + + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) + goto out; + + alive = TRUE; + if(*input_pending) { + CURLcode result; + /* This happens before we've sent off a request and the connection is + not in use by any other transfer, there shouldn't be any data here, + only "protocol frames" */ + *input_pending = FALSE; + result = cf_progress_ingress(cf, data, NULL); + CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); + alive = result? FALSE : TRUE; + } + +out: + CF_DATA_RESTORE(cf, save); + return alive; +} + +struct Curl_cftype Curl_cft_http3 = { + "HTTP/3", + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX, + 0, + cf_ngtcp2_destroy, + cf_ngtcp2_connect, + cf_ngtcp2_close, + Curl_cf_def_get_host, + cf_ngtcp2_adjust_pollset, + cf_ngtcp2_data_pending, + cf_ngtcp2_send, + cf_ngtcp2_recv, + cf_ngtcp2_data_event, + cf_ngtcp2_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_ngtcp2_query, +}; + +CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai) +{ + struct cf_ngtcp2_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL, *udp_cf = NULL; + CURLcode result; + + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->qlogfd = -1; + cf_ngtcp2_ctx_clear(ctx); + + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + if(result) + goto out; + + result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC); + if(result) + goto out; + + cf->conn = conn; + udp_cf->conn = cf->conn; + udp_cf->sockindex = cf->sockindex; + cf->next = udp_cf; + +out: + *pcf = (!result)? cf : NULL; + if(result) { + if(udp_cf) + Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE); + Curl_safefree(cf); + Curl_safefree(ctx); + } + return result; +} + +bool Curl_conn_is_ngtcp2(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + + (void)data; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_http3) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT) + return FALSE; + } + return FALSE; +} + +#endif diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h new file mode 100644 index 0000000..db3e611 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h @@ -0,0 +1,61 @@ +#ifndef HEADER_CURL_VQUIC_CURL_NGTCP2_H +#define HEADER_CURL_VQUIC_CURL_NGTCP2_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) + +#ifdef HAVE_NETINET_UDP_H +#include <netinet/udp.h> +#endif + +#include <ngtcp2/ngtcp2_crypto.h> +#include <nghttp3/nghttp3.h> +#ifdef USE_OPENSSL +#include <openssl/ssl.h> +#elif defined(USE_WOLFSSL) +#include <wolfssl/options.h> +#include <wolfssl/ssl.h> +#include <wolfssl/quic.h> +#endif + +struct Curl_cfilter; + +#include "urldata.h" + +void Curl_ngtcp2_ver(char *p, size_t len); + +CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai); + +bool Curl_conn_is_ngtcp2(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); +#endif + +#endif /* HEADER_CURL_VQUIC_CURL_NGTCP2_H */ diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.c b/Utilities/cmcurl/lib/vquic/curl_quiche.c new file mode 100644 index 0000000..7123d63 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_quiche.c @@ -0,0 +1,1717 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_QUICHE +#include <quiche.h> +#include <openssl/err.h> +#include <openssl/ssl.h> +#include "bufq.h" +#include "urldata.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "sendf.h" +#include "strdup.h" +#include "rand.h" +#include "strcase.h" +#include "multiif.h" +#include "connect.h" +#include "progress.h" +#include "strerror.h" +#include "http1.h" +#include "vquic.h" +#include "vquic_int.h" +#include "curl_quiche.h" +#include "transfer.h" +#include "inet_pton.h" +#include "vtls/openssl.h" +#include "vtls/keylog.h" +#include "vtls/vtls.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* HTTP/3 error values defined in RFC 9114, ch. 8.1 */ +#define CURL_H3_NO_ERROR (0x0100) + +#define QUIC_MAX_STREAMS (100) + +#define H3_STREAM_WINDOW_SIZE (128 * 1024) +#define H3_STREAM_CHUNK_SIZE (16 * 1024) +/* The pool keeps spares around and half of a full stream windows + * seems good. More does not seem to improve performance. + * The benefit of the pool is that stream buffer to not keep + * spares. So memory consumption goes down when streams run empty, + * have a large upload done, etc. */ +#define H3_STREAM_POOL_SPARES \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE ) / 2 +/* Receive and Send max number of chunks just follows from the + * chunk size and window size */ +#define H3_STREAM_RECV_CHUNKS \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) +#define H3_STREAM_SEND_CHUNKS \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) + +/* + * Store quiche version info in this buffer. + */ +void Curl_quiche_ver(char *p, size_t len) +{ + (void)msnprintf(p, len, "quiche/%s", quiche_version()); +} + +static void keylog_callback(const SSL *ssl, const char *line) +{ + (void)ssl; + Curl_tls_keylog_write_line(line); +} + +struct cf_quiche_ctx { + struct cf_quic_ctx q; + struct ssl_peer peer; + quiche_conn *qconn; + quiche_config *cfg; + quiche_h3_conn *h3c; + quiche_h3_config *h3config; + uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; + SSL_CTX *sslctx; + SSL *ssl; + struct curltime started_at; /* time the current attempt started */ + struct curltime handshake_at; /* time connect handshake finished */ + struct curltime first_byte_at; /* when first byte was recvd */ + struct curltime reconnect_at; /* time the next attempt should start */ + struct bufc_pool stream_bufcp; /* chunk pool for streams */ + curl_off_t data_recvd; + uint64_t max_idle_ms; /* max idle time for QUIC conn */ + BIT(goaway); /* got GOAWAY from server */ + BIT(got_first_byte); /* if first byte was received */ + BIT(x509_store_setup); /* if x509 store has been set up */ +}; + +#ifdef DEBUG_QUICHE +static void quiche_debug_log(const char *line, void *argp) +{ + (void)argp; + fprintf(stderr, "%s\n", line); +} +#endif + +static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx) +{ + if(ctx) { + vquic_ctx_free(&ctx->q); + if(ctx->qconn) + quiche_conn_free(ctx->qconn); + if(ctx->h3config) + quiche_h3_config_free(ctx->h3config); + if(ctx->h3c) + quiche_h3_conn_free(ctx->h3c); + if(ctx->cfg) + quiche_config_free(ctx->cfg); + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_ssl_peer_cleanup(&ctx->peer); + + memset(ctx, 0, sizeof(*ctx)); + } +} + +static CURLcode quic_x509_store_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct ssl_primary_config *conn_config; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + + if(!ctx->x509_store_setup) { + if(conn_config->verifypeer) { + const char * const ssl_cafile = conn_config->CAfile; + const char * const ssl_capath = conn_config->CApath; + if(ssl_cafile || ssl_capath) { + SSL_CTX_set_verify(ctx->sslctx, SSL_VERIFY_PEER, NULL); + /* tell OpenSSL where to find CA certificates that are used to verify + the server's certificate. */ + if(!SSL_CTX_load_verify_locations(ctx->sslctx, ssl_cafile, + ssl_capath)) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate verify locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + return CURLE_SSL_CACERT_BADFILE; + } + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + } +#ifdef CURL_CA_FALLBACK + else { + /* verifying the peer without any CA certificates won't work so + use openssl's built-in default as fallback */ + SSL_CTX_set_default_verify_paths(ctx->sslctx); + } +#endif + } + ctx->x509_store_setup = TRUE; + } + return CURLE_OK; +} + +static CURLcode quic_ssl_setup(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct ssl_primary_config *conn_config; + CURLcode result; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + + result = Curl_ssl_peer_init(&ctx->peer, cf); + if(result) + return result; + + DEBUGASSERT(!ctx->sslctx); + ctx->sslctx = SSL_CTX_new(TLS_method()); + if(!ctx->sslctx) + return CURLE_OUT_OF_MEMORY; + + SSL_CTX_set_alpn_protos(ctx->sslctx, + (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL, + sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); + + SSL_CTX_set_default_verify_paths(ctx->sslctx); + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { + SSL_CTX_set_keylog_callback(ctx->sslctx, keylog_callback); + } + + if(conn_config->curves && + !SSL_CTX_set1_curves_list(ctx->sslctx, conn_config->curves)) { + failf(data, "failed setting curves list for QUIC: '%s'", + conn_config->curves); + return CURLE_SSL_CIPHER; + } + + ctx->ssl = SSL_new(ctx->sslctx); + if(!ctx->ssl) + return CURLE_QUIC_CONNECT_ERROR; + + SSL_set_app_data(ctx->ssl, cf); + + if(ctx->peer.sni) { + if(!SSL_set_tlsext_host_name(ctx->ssl, ctx->peer.sni)) { + failf(data, "Failed set SNI"); + SSL_free(ctx->ssl); + ctx->ssl = NULL; + return CURLE_QUIC_CONNECT_ERROR; + } + } + + return CURLE_OK; +} + +/** + * All about the H3 internals of a stream + */ +struct stream_ctx { + int64_t id; /* HTTP/3 protocol stream identifier */ + struct bufq recvbuf; /* h3 response */ + struct h1_req_parser h1; /* h1 request parsing */ + uint64_t error3; /* HTTP/3 stream error code */ + curl_off_t upload_left; /* number of request bytes left to upload */ + bool closed; /* TRUE on stream close */ + bool reset; /* TRUE on stream reset */ + bool send_closed; /* stream is locally closed */ + bool resp_hds_complete; /* complete, final response has been received */ + bool resp_got_header; /* TRUE when h3 stream has recvd some HEADER */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ +}; + +#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ + ((struct HTTP *)(d)->req.p.http)->h3_ctx \ + : NULL)) +#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx +#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ + H3_STREAM_CTX(d)->id : -2) + +static void check_resumes(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct Curl_easy *sdata; + struct stream_ctx *stream; + + DEBUGASSERT(data->multi); + for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { + if(sdata->conn == data->conn) { + stream = H3_STREAM_CTX(sdata); + if(stream && stream->quic_flow_blocked) { + stream->quic_flow_blocked = FALSE; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + CURL_TRC_CF(data, cf, "[%"PRId64"] unblock", stream->id); + } + } + } +} + +static CURLcode h3_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(data); + + if(stream) + return CURLE_OK; + + stream = calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + H3_STREAM_LCTX(data) = stream; + stream->id = -1; + Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, + H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + return CURLE_OK; +} + +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(data); + + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->id); + if(ctx->qconn && !stream->closed) { + quiche_conn_stream_shutdown(ctx->qconn, stream->id, + QUICHE_SHUTDOWN_READ, CURL_H3_NO_ERROR); + if(!stream->send_closed) { + quiche_conn_stream_shutdown(ctx->qconn, stream->id, + QUICHE_SHUTDOWN_WRITE, CURL_H3_NO_ERROR); + stream->send_closed = TRUE; + } + stream->closed = TRUE; + } + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); + H3_STREAM_LCTX(data) = NULL; + } +} + +static void drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(stream && !stream->send_closed && stream->upload_left) + bits |= CURL_CSELECT_OUT; + if(data->state.dselect_bits != bits) { + data->state.dselect_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + +static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf, + struct Curl_easy *data, + int64_t stream3_id) +{ + struct Curl_easy *sdata; + + (void)cf; + if(H3_STREAM_ID(data) == stream3_id) { + return data; + } + else { + DEBUGASSERT(data->multi); + for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { + if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream3_id) { + return sdata; + } + } + } + return NULL; +} + +/* + * write_resp_raw() copies response data in raw format to the `data`'s + * receive buffer. If not enough space is available, it appends to the + * `data`'s overflow buffer. + */ +static CURLcode write_resp_raw(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, size_t memlen) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result = CURLE_OK; + ssize_t nwritten; + + (void)cf; + if(!stream) + return CURLE_RECV_ERROR; + nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result); + if(nwritten < 0) + return result; + + if((size_t)nwritten < memlen) { + /* This MUST not happen. Our recbuf is dimensioned to hold the + * full max_stream_window and then some for this very reason. */ + DEBUGASSERT(0); + return CURLE_RECV_ERROR; + } + return result; +} + +struct cb_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; +}; + +static int cb_each_header(uint8_t *name, size_t name_len, + uint8_t *value, size_t value_len, + void *argp) +{ + struct cb_ctx *x = argp; + struct stream_ctx *stream = H3_STREAM_CTX(x->data); + CURLcode result; + + if(!stream) + return CURLE_OK; + + if((name_len == 7) && !strncmp(HTTP_PSEUDO_STATUS, (char *)name, 7)) { + CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] status: %.*s", + stream->id, (int)value_len, value); + result = write_resp_raw(x->cf, x->data, "HTTP/3 ", sizeof("HTTP/3 ") - 1); + if(!result) + result = write_resp_raw(x->cf, x->data, value, value_len); + if(!result) + result = write_resp_raw(x->cf, x->data, " \r\n", 3); + } + else { + CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] header: %.*s: %.*s", + stream->id, (int)name_len, name, + (int)value_len, value); + result = write_resp_raw(x->cf, x->data, name, name_len); + if(!result) + result = write_resp_raw(x->cf, x->data, ": ", 2); + if(!result) + result = write_resp_raw(x->cf, x->data, value, value_len); + if(!result) + result = write_resp_raw(x->cf, x->data, "\r\n", 2); + } + if(result) { + CURL_TRC_CF(x->data, x->cf, "[%"PRId64"] on header error %d", + stream->id, result); + } + return result; +} + +static ssize_t stream_resp_read(void *reader_ctx, + unsigned char *buf, size_t len, + CURLcode *err) +{ + struct cb_ctx *x = reader_ctx; + struct cf_quiche_ctx *ctx = x->cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(x->data); + ssize_t nread; + + if(!stream) { + *err = CURLE_RECV_ERROR; + return -1; + } + + nread = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream->id, + buf, len); + if(nread >= 0) { + *err = CURLE_OK; + return nread; + } + else { + *err = CURLE_AGAIN; + return -1; + } +} + +static CURLcode cf_recv_body(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + ssize_t nwritten; + struct cb_ctx cb_ctx; + CURLcode result = CURLE_OK; + + if(!stream) + return CURLE_RECV_ERROR; + + if(!stream->resp_hds_complete) { + result = write_resp_raw(cf, data, "\r\n", 2); + if(result) + return result; + stream->resp_hds_complete = TRUE; + } + + cb_ctx.cf = cf; + cb_ctx.data = data; + nwritten = Curl_bufq_slurp(&stream->recvbuf, + stream_resp_read, &cb_ctx, &result); + + if(nwritten < 0 && result != CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "[%"PRId64"] recv_body error %zd", + stream->id, nwritten); + failf(data, "Error %d in HTTP/3 response body for stream[%"PRId64"]", + result, stream->id); + stream->closed = TRUE; + stream->reset = TRUE; + stream->send_closed = TRUE; + streamclose(cf->conn, "Reset of stream"); + return result; + } + return CURLE_OK; +} + +#ifdef DEBUGBUILD +static const char *cf_ev_name(quiche_h3_event *ev) +{ + switch(quiche_h3_event_type(ev)) { + case QUICHE_H3_EVENT_HEADERS: + return "HEADERS"; + case QUICHE_H3_EVENT_DATA: + return "DATA"; + case QUICHE_H3_EVENT_RESET: + return "RESET"; + case QUICHE_H3_EVENT_FINISHED: + return "FINISHED"; + case QUICHE_H3_EVENT_GOAWAY: + return "GOAWAY"; + default: + return "Unknown"; + } +} +#else +#define cf_ev_name(x) "" +#endif + +static CURLcode h3_process_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + int64_t stream3_id, + quiche_h3_event *ev) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cb_ctx cb_ctx; + CURLcode result = CURLE_OK; + int rc; + + if(!stream) + return CURLE_OK; + DEBUGASSERT(stream3_id == stream->id); + switch(quiche_h3_event_type(ev)) { + case QUICHE_H3_EVENT_HEADERS: + stream->resp_got_header = TRUE; + cb_ctx.cf = cf; + cb_ctx.data = data; + rc = quiche_h3_event_for_each_header(ev, cb_each_header, &cb_ctx); + if(rc) { + failf(data, "Error %d in HTTP/3 response header for stream[%"PRId64"]", + rc, stream3_id); + return CURLE_RECV_ERROR; + } + CURL_TRC_CF(data, cf, "[%"PRId64"] <- [HEADERS]", stream3_id); + break; + + case QUICHE_H3_EVENT_DATA: + if(!stream->closed) { + result = cf_recv_body(cf, data); + } + break; + + case QUICHE_H3_EVENT_RESET: + CURL_TRC_CF(data, cf, "[%"PRId64"] RESET", stream3_id); + stream->closed = TRUE; + stream->reset = TRUE; + stream->send_closed = TRUE; + streamclose(cf->conn, "Reset of stream"); + break; + + case QUICHE_H3_EVENT_FINISHED: + CURL_TRC_CF(data, cf, "[%"PRId64"] CLOSED", stream3_id); + if(!stream->resp_hds_complete) { + result = write_resp_raw(cf, data, "\r\n", 2); + if(result) + return result; + stream->resp_hds_complete = TRUE; + } + stream->closed = TRUE; + streamclose(cf->conn, "End of stream"); + break; + + case QUICHE_H3_EVENT_GOAWAY: + CURL_TRC_CF(data, cf, "[%"PRId64"] <- [GOAWAY]", stream3_id); + break; + + default: + CURL_TRC_CF(data, cf, "[%"PRId64"] recv, unhandled event %d", + stream3_id, quiche_h3_event_type(ev)); + break; + } + return result; +} + +static CURLcode cf_poll_events(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(data); + struct Curl_easy *sdata; + quiche_h3_event *ev; + CURLcode result; + + /* Take in the events and distribute them to the transfers. */ + while(ctx->h3c) { + int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev); + if(stream3_id == QUICHE_H3_ERR_DONE) { + break; + } + else if(stream3_id < 0) { + CURL_TRC_CF(data, cf, "[%"PRId64"] error poll: %"PRId64, + stream? stream->id : -1, stream3_id); + return CURLE_HTTP3; + } + + sdata = get_stream_easy(cf, data, stream3_id); + if(!sdata) { + CURL_TRC_CF(data, cf, "[%"PRId64"] discard event %s for " + "unknown [%"PRId64"]", + stream? stream->id : -1, cf_ev_name(ev), stream3_id); + } + else { + result = h3_process_event(cf, sdata, stream3_id, ev); + drain_stream(cf, sdata); + if(result) { + CURL_TRC_CF(data, cf, "[%"PRId64"] error processing event %s " + "for [%"PRId64"] -> %d", + stream? stream->id : -1, cf_ev_name(ev), + stream3_id, result); + if(data == sdata) { + /* Only report this error to the caller if it is about the + * transfer we were called with. Otherwise we fail a transfer + * due to a problem in another one. */ + quiche_h3_event_free(ev); + return result; + } + } + quiche_h3_event_free(ev); + } + } + return CURLE_OK; +} + +struct recv_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; + int pkts; +}; + +static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, + struct sockaddr_storage *remote_addr, + socklen_t remote_addrlen, int ecn, + void *userp) +{ + struct recv_ctx *r = userp; + struct cf_quiche_ctx *ctx = r->cf->ctx; + quiche_recv_info recv_info; + ssize_t nread; + + (void)ecn; + ++r->pkts; + + recv_info.to = (struct sockaddr *)&ctx->q.local_addr; + recv_info.to_len = ctx->q.local_addrlen; + recv_info.from = (struct sockaddr *)remote_addr; + recv_info.from_len = remote_addrlen; + + nread = quiche_conn_recv(ctx->qconn, (unsigned char *)pkt, pktlen, + &recv_info); + if(nread < 0) { + if(QUICHE_ERR_DONE == nread) { + CURL_TRC_CF(r->data, r->cf, "ingress, quiche is DONE"); + return CURLE_OK; + } + else if(QUICHE_ERR_TLS_FAIL == nread) { + long verify_ok = SSL_get_verify_result(ctx->ssl); + if(verify_ok != X509_V_OK) { + failf(r->data, "SSL certificate problem: %s", + X509_verify_cert_error_string(verify_ok)); + return CURLE_PEER_FAILED_VERIFICATION; + } + } + else { + failf(r->data, "quiche_conn_recv() == %zd", nread); + return CURLE_RECV_ERROR; + } + } + else if((size_t)nread < pktlen) { + CURL_TRC_CF(r->data, r->cf, "ingress, quiche only read %zd/%zu bytes", + nread, pktlen); + } + + return CURLE_OK; +} + +static CURLcode cf_process_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct recv_ctx rctx; + CURLcode result; + + DEBUGASSERT(ctx->qconn); + result = quic_x509_store_setup(cf, data); + if(result) + return result; + + rctx.cf = cf; + rctx.data = data; + rctx.pkts = 0; + + result = vquic_recv_packets(cf, data, &ctx->q, 1000, recv_pkt, &rctx); + if(result) + return result; + + if(rctx.pkts > 0) { + /* quiche digested ingress packets. It might have opened flow control + * windows again. */ + check_resumes(cf, data); + } + return cf_poll_events(cf, data); +} + +struct read_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; + quiche_send_info send_info; +}; + +static ssize_t read_pkt_to_send(void *userp, + unsigned char *buf, size_t buflen, + CURLcode *err) +{ + struct read_ctx *x = userp; + struct cf_quiche_ctx *ctx = x->cf->ctx; + ssize_t nwritten; + + nwritten = quiche_conn_send(ctx->qconn, buf, buflen, &x->send_info); + if(nwritten == QUICHE_ERR_DONE) { + *err = CURLE_AGAIN; + return -1; + } + + if(nwritten < 0) { + failf(x->data, "quiche_conn_send returned %zd", nwritten); + *err = CURLE_SEND_ERROR; + return -1; + } + *err = CURLE_OK; + return nwritten; +} + +/* + * flush_egress drains the buffers and sends off data. + * Calls failf() on errors. + */ +static CURLcode cf_flush_egress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + ssize_t nread; + CURLcode result; + int64_t expiry_ns; + int64_t timeout_ns; + struct read_ctx readx; + size_t pkt_count, gsolen; + + expiry_ns = quiche_conn_timeout_as_nanos(ctx->qconn); + if(!expiry_ns) { + quiche_conn_on_timeout(ctx->qconn); + if(quiche_conn_is_closed(ctx->qconn)) { + failf(data, "quiche_conn_on_timeout closed the connection"); + return CURLE_SEND_ERROR; + } + } + + result = vquic_flush(cf, data, &ctx->q); + if(result) { + if(result == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return result; + } + + readx.cf = cf; + readx.data = data; + memset(&readx.send_info, 0, sizeof(readx.send_info)); + pkt_count = 0; + gsolen = quiche_conn_max_send_udp_payload_size(ctx->qconn); + for(;;) { + /* add the next packet to send, if any, to our buffer */ + nread = Curl_bufq_sipn(&ctx->q.sendbuf, 0, + read_pkt_to_send, &readx, &result); + if(nread < 0) { + if(result != CURLE_AGAIN) + return result; + /* Nothing more to add, flush and leave */ + result = vquic_send(cf, data, &ctx->q, gsolen); + if(result) { + if(result == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return result; + } + goto out; + } + + ++pkt_count; + if((size_t)nread < gsolen || pkt_count >= MAX_PKT_BURST) { + result = vquic_send(cf, data, &ctx->q, gsolen); + if(result) { + if(result == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + goto out; + } + pkt_count = 0; + } + } + +out: + timeout_ns = quiche_conn_timeout_as_nanos(ctx->qconn); + if(timeout_ns % 1000000) + timeout_ns += 1000000; + /* expire resolution is milliseconds */ + Curl_expire(data, (timeout_ns / 1000000), EXPIRE_QUIC); + return result; +} + +static ssize_t recv_closed_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + CURLcode *err) +{ + struct stream_ctx *stream = H3_STREAM_CTX(data); + ssize_t nread = -1; + + DEBUGASSERT(stream); + if(stream->reset) { + failf(data, + "HTTP/3 stream %" PRId64 " reset by server", stream->id); + *err = stream->resp_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv, was reset -> %d", + stream->id, *err); + } + else if(!stream->resp_got_header) { + failf(data, + "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" + " all response header fields, treated as error", + stream->id); + /* *err = CURLE_PARTIAL_FILE; */ + *err = CURLE_RECV_ERROR; + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv, closed incomplete" + " -> %d", stream->id, *err); + } + else { + *err = CURLE_OK; + nread = 0; + } + return nread; +} + +static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(data); + ssize_t nread = -1; + CURLcode result; + + vquic_ctx_update_time(&ctx->q); + + if(!stream) { + *err = CURLE_RECV_ERROR; + return -1; + } + + if(!Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->id, len, nread, *err); + if(nread < 0) + goto out; + } + + if(cf_process_ingress(cf, data)) { + CURL_TRC_CF(data, cf, "cf_recv, error on ingress"); + *err = CURLE_RECV_ERROR; + nread = -1; + goto out; + } + + /* recvbuf had nothing before, maybe after progressing ingress? */ + if(nread < 0 && !Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->id, len, nread, *err); + if(nread < 0) + goto out; + } + + if(nread > 0) { + if(stream->closed) + drain_stream(cf, data); + } + else { + if(stream->closed) { + nread = recv_closed_stream(cf, data, err); + goto out; + } + else if(quiche_conn_is_draining(ctx->qconn)) { + failf(data, "QUIC connection is draining"); + *err = CURLE_HTTP3; + nread = -1; + goto out; + } + *err = CURLE_AGAIN; + nread = -1; + } + +out: + result = cf_flush_egress(cf, data); + if(result) { + CURL_TRC_CF(data, cf, "cf_recv, flush egress failed"); + *err = result; + nread = -1; + } + if(nread > 0) + ctx->data_recvd += nread; + CURL_TRC_CF(data, cf, "[%"PRId64"] cf_recv(total=%" + CURL_FORMAT_CURL_OFF_T ") -> %zd, %d", + stream->id, ctx->data_recvd, nread, *err); + return nread; +} + +/* Index where :authority header field will appear in request header + field list. */ +#define AUTHORITY_DST_IDX 3 + +static ssize_t h3_open_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t len, + CURLcode *err) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(data); + size_t nheader, i; + int64_t stream3_id; + struct dynhds h2_headers; + quiche_h3_header *nva = NULL; + ssize_t nwritten; + + if(!stream) { + *err = h3_data_setup(cf, data); + if(*err) { + return -1; + } + stream = H3_STREAM_CTX(data); + DEBUGASSERT(stream); + } + + Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + + DEBUGASSERT(stream); + nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); + if(nwritten < 0) + goto out; + if(!stream->h1.done) { + /* need more data */ + goto out; + } + DEBUGASSERT(stream->h1.req); + + *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); + if(*err) { + nwritten = -1; + goto out; + } + /* no longer needed */ + Curl_h1_req_parse_free(&stream->h1); + + nheader = Curl_dynhds_count(&h2_headers); + nva = malloc(sizeof(quiche_h3_header) * nheader); + if(!nva) { + *err = CURLE_OUT_OF_MEMORY; + nwritten = -1; + goto out; + } + + for(i = 0; i < nheader; ++i) { + struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); + nva[i].name = (unsigned char *)e->name; + nva[i].name_len = e->namelen; + nva[i].value = (unsigned char *)e->value; + nva[i].value_len = e->valuelen; + } + + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + case HTTPREQ_PUT: + if(data->state.infilesize != -1) + stream->upload_left = data->state.infilesize; + else + /* data sending without specifying the data amount up front */ + stream->upload_left = -1; /* unknown */ + break; + default: + stream->upload_left = 0; /* no request body */ + break; + } + + if(stream->upload_left == 0) + stream->send_closed = TRUE; + + stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader, + stream->send_closed); + if(stream3_id < 0) { + if(QUICHE_H3_ERR_STREAM_BLOCKED == stream3_id) { + /* quiche seems to report this error if the connection window is + * exhausted. Which happens frequently and intermittent. */ + CURL_TRC_CF(data, cf, "[%"PRId64"] blocked", stream->id); + stream->quic_flow_blocked = TRUE; + *err = CURLE_AGAIN; + nwritten = -1; + goto out; + } + else { + CURL_TRC_CF(data, cf, "send_request(%s) -> %" PRId64, + data->state.url, stream3_id); + } + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + + DEBUGASSERT(stream->id == -1); + *err = CURLE_OK; + stream->id = stream3_id; + stream->closed = FALSE; + stream->reset = FALSE; + + if(Curl_trc_is_verbose(data)) { + infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s", + stream->id, data->state.url); + for(i = 0; i < nheader; ++i) { + infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", stream->id, + (int)nva[i].name_len, nva[i].name, + (int)nva[i].value_len, nva[i].value); + } + } + +out: + free(nva); + Curl_dynhds_free(&h2_headers); + return nwritten; +} + +static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result; + ssize_t nwritten; + + vquic_ctx_update_time(&ctx->q); + + *err = cf_process_ingress(cf, data); + if(*err) { + nwritten = -1; + goto out; + } + + if(!stream || stream->id < 0) { + nwritten = h3_open_stream(cf, data, buf, len, err); + if(nwritten < 0) + goto out; + stream = H3_STREAM_CTX(data); + } + else { + bool eof = (stream->upload_left >= 0 && + (curl_off_t)len >= stream->upload_left); + nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->id, + (uint8_t *)buf, len, eof); + if(nwritten == QUICHE_H3_ERR_DONE || (nwritten == 0 && len > 0)) { + /* TODO: we seem to be blocked on flow control and should HOLD + * sending. But when do we open again? */ + if(!quiche_conn_stream_writable(ctx->qconn, stream->id, len)) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + "-> window exhausted", stream->id, len); + stream->quic_flow_blocked = TRUE; + } + *err = CURLE_AGAIN; + nwritten = -1; + goto out; + } + else if(nwritten == QUICHE_H3_TRANSPORT_ERR_INVALID_STREAM_STATE && + stream->closed && stream->resp_hds_complete) { + /* sending request body on a stream that has been closed by the + * server. If the server has send us a final response, we should + * silently discard the send data. + * This happens for example on redirects where the server, instead + * of reading the full request body just closed the stream after + * sending the 30x response. + * This is sort of a race: had the transfer loop called recv first, + * it would see the response and stop/discard sending on its own- */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data" + "on closed stream with response", stream->id); + *err = CURLE_OK; + nwritten = (ssize_t)len; + goto out; + } + else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + "-> exceeds size", stream->id, len); + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + else if(nwritten < 0) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + "-> quiche err %zd", stream->id, len, nwritten); + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + else { + /* quiche accepted all or at least a part of the buf */ + if(stream->upload_left > 0) { + stream->upload_left = (nwritten < stream->upload_left)? + (stream->upload_left - nwritten) : 0; + } + if(stream->upload_left == 0) + stream->send_closed = TRUE; + + CURL_TRC_CF(data, cf, "[%" PRId64 "] send body(len=%zu, " + "left=%" CURL_FORMAT_CURL_OFF_T ") -> %zd", + stream->id, len, stream->upload_left, nwritten); + *err = CURLE_OK; + } + } + +out: + result = cf_flush_egress(cf, data); + if(result) { + *err = result; + nwritten = -1; + } + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %zd, %d", + stream? stream->id : -1, len, nwritten, *err); + return nwritten; +} + +static bool stream_is_writeable(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(data); + + return stream && (quiche_conn_stream_writable(ctx->qconn, + (uint64_t)stream->id, 1) > 0); +} + +static void cf_quiche_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + bool want_recv = CURL_WANT_RECV(data); + bool want_send = CURL_WANT_SEND(data); + + if(ctx->qconn && (want_recv || want_send)) { + struct stream_ctx *stream = H3_STREAM_CTX(data); + bool c_exhaust, s_exhaust; + + c_exhaust = FALSE; /* Have not found any call in quiche that tells + us if the connection itself is blocked */ + s_exhaust = stream && stream->id >= 0 && + (stream->quic_flow_blocked || !stream_is_writeable(cf, data)); + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + !Curl_bufq_is_empty(&ctx->q.sendbuf); + + Curl_pollset_set(data, ps, ctx->q.sockfd, want_recv, want_send); + } +} + +/* + * Called from transfer.c:data_pending to know if we should keep looping + * to receive more data from the connection. + */ +static bool cf_quiche_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + const struct stream_ctx *stream = H3_STREAM_CTX(data); + (void)cf; + return stream && !Curl_bufq_is_empty(&stream->recvbuf); +} + +static CURLcode h3_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ + /* TODO: there seems right now no API in quiche to shrink/enlarge + * the streams windows. As we do in HTTP/2. */ + if(!pause) { + drain_stream(cf, data); + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + return CURLE_OK; +} + +static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + CURLcode result = CURLE_OK; + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_DATA_SETUP: + break; + case CF_CTRL_DATA_PAUSE: + result = h3_data_pause(cf, data, (arg1 != 0)); + break; + case CF_CTRL_DATA_DETACH: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE_SEND: { + struct stream_ctx *stream = H3_STREAM_CTX(data); + if(stream && !stream->send_closed) { + unsigned char body[1]; + ssize_t sent; + + stream->send_closed = TRUE; + stream->upload_left = 0; + body[0] = 'X'; + sent = cf_quiche_send(cf, data, body, 0, &result); + CURL_TRC_CF(data, cf, "[%"PRId64"] DONE_SEND -> %zd, %d", + stream->id, sent, result); + } + break; + } + case CF_CTRL_DATA_IDLE: { + struct stream_ctx *stream = H3_STREAM_CTX(data); + if(stream && !stream->closed) { + result = cf_flush_egress(cf, data); + if(result) + CURL_TRC_CF(data, cf, "data idle, flush egress -> %d", result); + } + break; + } + default: + break; + } + return result; +} + +static CURLcode cf_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct ssl_primary_config *conn_config; + CURLcode result = CURLE_OK; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; + + if(conn_config->verifyhost) { + X509 *server_cert; + server_cert = SSL_get_peer_certificate(ctx->ssl); + if(!server_cert) { + result = CURLE_PEER_FAILED_VERIFICATION; + goto out; + } + result = Curl_ossl_verifyhost(data, cf->conn, &ctx->peer, server_cert); + X509_free(server_cert); + if(result) + goto out; + } + else + CURL_TRC_CF(data, cf, "Skipped certificate verification"); + + ctx->h3config = quiche_h3_config_new(); + if(!ctx->h3config) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + /* Create a new HTTP/3 connection on the QUIC connection. */ + ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config); + if(!ctx->h3c) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + if(data->set.ssl.certinfo) + /* asked to gather certificate info */ + (void)Curl_ossl_certchain(data, ctx->ssl); + +out: + if(result) { + if(ctx->h3config) { + quiche_h3_config_free(ctx->h3config); + ctx->h3config = NULL; + } + if(ctx->h3c) { + quiche_h3_conn_free(ctx->h3c); + ctx->h3c = NULL; + } + } + return result; +} + +static CURLcode cf_connect_start(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + int rv; + CURLcode result; + const struct Curl_sockaddr_ex *sockaddr; + + DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD); + +#ifdef DEBUG_QUICHE + /* initialize debug log callback only once */ + static int debug_log_init = 0; + if(!debug_log_init) { + quiche_enable_debug_logging(quiche_debug_log, NULL); + debug_log_init = 1; + } +#endif + ctx->max_idle_ms = CURL_QUIC_MAX_IDLE_MS; + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + ctx->data_recvd = 0; + + result = vquic_ctx_init(&ctx->q); + if(result) + return result; + + ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION); + if(!ctx->cfg) { + failf(data, "can't create quiche config"); + return CURLE_FAILED_INIT; + } + quiche_config_enable_pacing(ctx->cfg, false); + quiche_config_set_max_idle_timeout(ctx->cfg, ctx->max_idle_ms * 1000); + quiche_config_set_initial_max_data(ctx->cfg, (1 * 1024 * 1024) + /* (QUIC_MAX_STREAMS/2) * H3_STREAM_WINDOW_SIZE */); + quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS); + quiche_config_set_initial_max_streams_uni(ctx->cfg, QUIC_MAX_STREAMS); + quiche_config_set_initial_max_stream_data_bidi_local(ctx->cfg, + H3_STREAM_WINDOW_SIZE); + quiche_config_set_initial_max_stream_data_bidi_remote(ctx->cfg, + H3_STREAM_WINDOW_SIZE); + quiche_config_set_initial_max_stream_data_uni(ctx->cfg, + H3_STREAM_WINDOW_SIZE); + quiche_config_set_disable_active_migration(ctx->cfg, TRUE); + + quiche_config_set_max_connection_window(ctx->cfg, + 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 *) + QUICHE_H3_APPLICATION_PROTOCOL, + sizeof(QUICHE_H3_APPLICATION_PROTOCOL) + - 1); + + DEBUGASSERT(!ctx->ssl); + DEBUGASSERT(!ctx->sslctx); + result = quic_ssl_setup(cf, data); + if(result) + return result; + + result = Curl_rand(data, ctx->scid, sizeof(ctx->scid)); + if(result) + return result; + + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, + &sockaddr, NULL, NULL, NULL, NULL); + ctx->q.local_addrlen = sizeof(ctx->q.local_addr); + rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, + &ctx->q.local_addrlen); + if(rv == -1) + return CURLE_QUIC_CONNECT_ERROR; + + ctx->qconn = quiche_conn_new_with_tls((const uint8_t *)ctx->scid, + sizeof(ctx->scid), NULL, 0, + (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen, + &sockaddr->sa_addr, sockaddr->addrlen, + ctx->cfg, ctx->ssl, false); + if(!ctx->qconn) { + failf(data, "can't create quiche connection"); + return CURLE_OUT_OF_MEMORY; + } + + /* Known to not work on Windows */ +#if !defined(_WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD) + { + int qfd; + (void)Curl_qlogdir(data, ctx->scid, sizeof(ctx->scid), &qfd); + if(qfd != -1) + quiche_conn_set_qlog_fd(ctx->qconn, qfd, + "qlog title", "curl qlog"); + } +#endif + + result = cf_flush_egress(cf, data); + if(result) + return result; + + { + unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL; + unsigned alpn_len, offset = 0; + + /* Replace each ALPN length prefix by a comma. */ + while(offset < sizeof(alpn_protocols) - 1) { + alpn_len = alpn_protocols[offset]; + alpn_protocols[offset] = ','; + offset += 1 + alpn_len; + } + + CURL_TRC_CF(data, cf, "Sent QUIC client Initial, ALPN: %s", + alpn_protocols + 1); + } + + return CURLE_OK; +} + +static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* Connect the UDP filter first */ + if(!cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + } + + *done = FALSE; + vquic_ctx_update_time(&ctx->q); + + if(ctx->reconnect_at.tv_sec && + Curl_timediff(ctx->q.last_op, ctx->reconnect_at) < 0) { + /* Not time yet to attempt the next connect */ + CURL_TRC_CF(data, cf, "waiting for reconnect time"); + goto out; + } + + if(!ctx->qconn) { + result = cf_connect_start(cf, data); + if(result) + goto out; + ctx->started_at = ctx->q.last_op; + result = cf_flush_egress(cf, data); + /* we do not expect to be able to recv anything yet */ + goto out; + } + + result = cf_process_ingress(cf, data); + if(result) + goto out; + + result = cf_flush_egress(cf, data); + if(result) + goto out; + + if(quiche_conn_is_established(ctx->qconn)) { + ctx->handshake_at = ctx->q.last_op; + CURL_TRC_CF(data, cf, "handshake complete after %dms", + (int)Curl_timediff(ctx->handshake_at, ctx->started_at)); + result = cf_verify_peer(cf, data); + if(!result) { + CURL_TRC_CF(data, cf, "peer verified"); + cf->connected = TRUE; + cf->conn->alpn = CURL_HTTP_VERSION_3; + *done = TRUE; + connkeep(cf->conn, "HTTP/3 default"); + } + } + else if(quiche_conn_is_draining(ctx->qconn)) { + /* When a QUIC server instance is shutting down, it may send us a + * CONNECTION_CLOSE right away. Our connection then enters the DRAINING + * state. The CONNECT may work in the near future again. Indicate + * that as a "weird" reply. */ + result = CURLE_WEIRD_SERVER_REPLY; + } + +out: +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(result && result != CURLE_AGAIN) { + const char *r_ip; + int r_port; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, + &r_ip, &r_port, NULL, NULL); + infof(data, "connect to %s port %u failed: %s", + r_ip, r_port, curl_easy_strerror(result)); + } +#endif + return result; +} + +static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + + if(ctx) { + if(ctx->qconn) { + vquic_ctx_update_time(&ctx->q); + (void)quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0); + /* flushing the egress is not a failsafe way to deliver all the + outstanding packets, but we also don't want to get stuck here... */ + (void)cf_flush_egress(cf, data); + } + cf_quiche_ctx_clear(ctx); + } +} + +static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + + (void)data; + cf_quiche_ctx_clear(ctx); + free(ctx); + cf->ctx = NULL; +} + +static CURLcode cf_quiche_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: { + uint64_t max_streams = CONN_INUSE(cf->conn); + if(!ctx->goaway) { + max_streams += quiche_conn_peer_streams_left_bidi(ctx->qconn); + } + *pres1 = (max_streams > INT_MAX)? INT_MAX : (int)max_streams; + CURL_TRC_CF(data, cf, "query: MAX_CONCURRENT -> %d", *pres1); + return CURLE_OK; + } + case CF_QUERY_CONNECT_REPLY_MS: + if(ctx->got_first_byte) { + timediff_t ms = Curl_timediff(ctx->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->got_first_byte) + *when = ctx->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; + } + default: + break; + } + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + bool alive = TRUE; + + *input_pending = FALSE; + if(!ctx->qconn) + return FALSE; + + /* Both sides of the QUIC connection announce they max idle times in + * the transport parameters. Look at the minimum of both and if + * we exceed this, regard the connection as dead. The other side + * may have completely purged it and will no longer respond + * to any packets from us. */ + { + quiche_transport_params qpeerparams; + timediff_t idletime; + uint64_t idle_ms = ctx->max_idle_ms; + + if(quiche_conn_peer_transport_params(ctx->qconn, &qpeerparams) && + qpeerparams.peer_max_idle_timeout && + qpeerparams.peer_max_idle_timeout < idle_ms) + idle_ms = qpeerparams.peer_max_idle_timeout; + idletime = Curl_timediff(Curl_now(), cf->conn->lastused); + if(idletime > 0 && (uint64_t)idletime > idle_ms) + return FALSE; + } + + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) + return FALSE; + + if(*input_pending) { + /* This happens before we've sent off a request and the connection is + not in use by any other transfer, there shouldn't be any data here, + only "protocol frames" */ + *input_pending = FALSE; + if(cf_process_ingress(cf, data)) + alive = FALSE; + else { + alive = TRUE; + } + } + + return alive; +} + +struct Curl_cftype Curl_cft_http3 = { + "HTTP/3", + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX, + 0, + cf_quiche_destroy, + cf_quiche_connect, + cf_quiche_close, + Curl_cf_def_get_host, + cf_quiche_adjust_pollset, + cf_quiche_data_pending, + cf_quiche_send, + cf_quiche_recv, + cf_quiche_data_event, + cf_quiche_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_quiche_query, +}; + +CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai) +{ + struct cf_quiche_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL, *udp_cf = NULL; + CURLcode result; + + (void)data; + (void)conn; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + if(result) + goto out; + + result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC); + if(result) + goto out; + + udp_cf->conn = cf->conn; + udp_cf->sockindex = cf->sockindex; + cf->next = udp_cf; + +out: + *pcf = (!result)? cf : NULL; + if(result) { + if(udp_cf) + Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE); + Curl_safefree(cf); + Curl_safefree(ctx); + } + + return result; +} + +bool Curl_conn_is_quiche(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + + (void)data; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_http3) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT) + return FALSE; + } + return FALSE; +} + +#endif diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.h b/Utilities/cmcurl/lib/vquic/curl_quiche.h new file mode 100644 index 0000000..bce781c --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_quiche.h @@ -0,0 +1,50 @@ +#ifndef HEADER_CURL_VQUIC_CURL_QUICHE_H +#define HEADER_CURL_VQUIC_CURL_QUICHE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_QUICHE + +#include <quiche.h> +#include <openssl/ssl.h> + +struct Curl_cfilter; +struct Curl_easy; + +void Curl_quiche_ver(char *p, size_t len); + +CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai); + +bool Curl_conn_is_quiche(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); + +#endif + +#endif /* HEADER_CURL_VQUIC_CURL_QUICHE_H */ diff --git a/Utilities/cmcurl/lib/vquic/vquic.c b/Utilities/cmcurl/lib/vquic/vquic.c new file mode 100644 index 0000000..523b807 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/vquic.c @@ -0,0 +1,671 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* WIP, experimental: use recvmmsg() on linux + * we have no configure check, yet + * and also it is only available for _GNU_SOURCE, which + * we do not use otherwise. +#define HAVE_SENDMMSG + */ +#if defined(HAVE_SENDMMSG) +#define _GNU_SOURCE +#include <sys/socket.h> +#undef _GNU_SOURCE +#endif + +#include "curl_setup.h" + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "urldata.h" +#include "bufq.h" +#include "dynbuf.h" +#include "cfilters.h" +#include "curl_trc.h" +#include "curl_msh3.h" +#include "curl_ngtcp2.h" +#include "curl_quiche.h" +#include "rand.h" +#include "vquic.h" +#include "vquic_int.h" +#include "strerror.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +#ifdef ENABLE_QUIC + +#ifdef O_BINARY +#define QLOGMODE O_WRONLY|O_CREAT|O_BINARY +#else +#define QLOGMODE O_WRONLY|O_CREAT +#endif + +#define NW_CHUNK_SIZE (64 * 1024) +#define NW_SEND_CHUNKS 2 + + +void Curl_quic_ver(char *p, size_t len) +{ +#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) + Curl_ngtcp2_ver(p, len); +#elif defined(USE_QUICHE) + Curl_quiche_ver(p, len); +#elif defined(USE_MSH3) + Curl_msh3_ver(p, len); +#endif +} + +CURLcode vquic_ctx_init(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; +#else + qctx->no_gso = TRUE; +#endif +#ifdef DEBUGBUILD + { + char *p = getenv("CURL_DBG_QUIC_WBLOCK"); + if(p) { + long l = strtol(p, NULL, 10); + if(l >= 0 && l <= 100) + qctx->wblock_percent = (int)l; + } + } +#endif + vquic_ctx_update_time(qctx); + + return CURLE_OK; +} + +void vquic_ctx_free(struct cf_quic_ctx *qctx) +{ + Curl_bufq_free(&qctx->sendbuf); +} + +void vquic_ctx_update_time(struct cf_quic_ctx *qctx) +{ + qctx->last_op = Curl_now(); +} + +static CURLcode send_packet_no_gso(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); + +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) +{ +#ifdef HAVE_SENDMSG + struct iovec msg_iov; + struct msghdr msg = {0}; + ssize_t sent; +#if defined(__linux__) && defined(UDP_SEGMENT) + uint8_t msg_ctrl[32]; + struct cmsghdr *cm; +#endif + + *psent = 0; + msg_iov.iov_base = (uint8_t *)pkt; + msg_iov.iov_len = pktlen; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + +#if defined(__linux__) && defined(UDP_SEGMENT) + if(pktlen > gsolen) { + /* Only set this, when we need it. macOS, for example, + * does not seem to like a msg_control of length 0. */ + msg.msg_control = msg_ctrl; + assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t))); + msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); + cm = CMSG_FIRSTHDR(&msg); + cm->cmsg_level = SOL_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff; + } +#endif + + + while((sent = sendmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR) + ; + + if(sent == -1) { + switch(SOCKERRNO) { + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + return CURLE_AGAIN; + case EMSGSIZE: + /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */ + break; + case EIO: + if(pktlen > gsolen) { + /* GSO failure */ + failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent, + SOCKERRNO); + qctx->no_gso = TRUE; + return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); + } + /* FALLTHROUGH */ + default: + failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO); + return CURLE_SEND_ERROR; + } + } + else { + assert(pktlen == (size_t)sent); + } +#else + ssize_t sent; + (void)gsolen; + + *psent = 0; + + while((sent = send(qctx->sockfd, + (const char *)pkt, (SEND_TYPE_ARG3)pktlen, 0)) == -1 && + SOCKERRNO == EINTR) + ; + + if(sent == -1) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + return CURLE_AGAIN; + } + else { + failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO); + if(SOCKERRNO != EMSGSIZE) { + return CURLE_SEND_ERROR; + } + /* UDP datagram is too large; caused by PMTUD. Just let it be + lost. */ + } + } +#endif + (void)cf; + *psent = pktlen; + + return CURLE_OK; +} + +static CURLcode send_packet_no_gso(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) +{ + const uint8_t *p, *end = pkt + pktlen; + size_t sent; + + *psent = 0; + + for(p = pkt; p < end; p += gsolen) { + size_t len = CURLMIN(gsolen, (size_t)(end - p)); + CURLcode curlcode = do_sendmsg(cf, data, qctx, p, len, len, &sent); + if(curlcode != CURLE_OK) { + return curlcode; + } + *psent += sent; + } + + return CURLE_OK; +} + +static CURLcode vquic_send_packets(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) +{ + CURLcode result; +#ifdef DEBUGBUILD + /* simulate network blocking/partial writes */ + if(qctx->wblock_percent > 0) { + unsigned char c; + Curl_rand(data, &c, 1); + if(c >= ((100-qctx->wblock_percent)*256/100)) { + CURL_TRC_CF(data, cf, "vquic_flush() simulate EWOULDBLOCK"); + return CURLE_AGAIN; + } + } +#endif + if(qctx->no_gso && pktlen > gsolen) { + result = send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); + } + else { + result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, 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) +{ + const unsigned char *buf; + size_t blen, sent; + CURLcode result; + size_t gsolen; + + while(Curl_bufq_peek(&qctx->sendbuf, &buf, &blen)) { + gsolen = qctx->gsolen; + if(qctx->split_len) { + gsolen = qctx->split_gsolen; + if(blen > qctx->split_len) + blen = qctx->split_len; + } + + result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent); + CURL_TRC_CF(data, cf, "vquic_send(len=%zu, gso=%zu) -> %d, sent=%zu", + blen, gsolen, result, sent); + if(result) { + if(result == CURLE_AGAIN) { + Curl_bufq_skip(&qctx->sendbuf, sent); + if(qctx->split_len) + qctx->split_len -= sent; + } + return result; + } + Curl_bufq_skip(&qctx->sendbuf, sent); + if(qctx->split_len) + qctx->split_len -= sent; + } + return CURLE_OK; +} + +CURLcode 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); +} + +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) +{ + DEBUGASSERT(Curl_bufq_len(&qctx->sendbuf) > tail_len); + qctx->split_len = Curl_bufq_len(&qctx->sendbuf) - tail_len; + qctx->split_gsolen = gsolen; + 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); +} + +#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_pkt_cb *recv_cb, void *userp) +{ +#define MMSG_NUM 64 + struct iovec msg_iov[MMSG_NUM]; + struct mmsghdr mmsg[MMSG_NUM]; + uint8_t bufs[MMSG_NUM][2*1024]; + struct sockaddr_storage remote_addr[MMSG_NUM]; + size_t total_nread, pkts; + int mcount, i, n; + char errstr[STRERROR_LEN]; + CURLcode result = CURLE_OK; + + DEBUGASSERT(max_pkts > 0); + pkts = 0; + total_nread = 0; + while(pkts < max_pkts) { + n = (int)CURLMIN(MMSG_NUM, 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 = (int)sizeof(bufs[i]); + mmsg[i].msg_hdr.msg_iov = &msg_iov[i]; + 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]); + } + + while((mcount = recvmmsg(qctx->sockfd, mmsg, n, 0, NULL)) == -1 && + SOCKERRNO == EINTR) + ; + if(mcount == -1) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + CURL_TRC_CF(data, cf, "ingress, recvmmsg -> EAGAIN"); + goto out; + } + if(!cf->connected && SOCKERRNO == ECONNREFUSED) { + const char *r_ip = NULL; + int r_port = 0; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, + &r_ip, &r_port, NULL, NULL); + failf(data, "QUIC: connection to %s port %u refused", + r_ip, r_port); + result = CURLE_COULDNT_CONNECT; + goto out; + } + Curl_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: recvmsg() unexpectedly returned %d (errno=%d; %s)", + mcount, SOCKERRNO, errstr); + result = CURLE_RECV_ERROR; + goto out; + } + + CURL_TRC_CF(data, cf, "recvmmsg() -> %d packets", mcount); + pkts += mcount; + for(i = 0; i < mcount; ++i) { + total_nread += mmsg[i].msg_len; + result = recv_cb(bufs[i], mmsg[i].msg_len, + mmsg[i].msg_hdr.msg_name, mmsg[i].msg_hdr.msg_namelen, + 0, userp); + if(result) + goto out; + } + } + +out: + if(total_nread || result) + CURL_TRC_CF(data, cf, "recvd %zu packets with %zu bytes -> %d", + pkts, total_nread, result); + 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_pkt_cb *recv_cb, void *userp) +{ + struct iovec msg_iov; + struct msghdr msg; + uint8_t buf[64*1024]; + struct sockaddr_storage remote_addr; + size_t total_nread, pkts; + ssize_t nread; + char errstr[STRERROR_LEN]; + CURLcode result = CURLE_OK; + + msg_iov.iov_base = buf; + msg_iov.iov_len = (int)sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + DEBUGASSERT(max_pkts > 0); + for(pkts = 0, total_nread = 0; pkts < max_pkts;) { + msg.msg_name = &remote_addr; + msg.msg_namelen = sizeof(remote_addr); + while((nread = recvmsg(qctx->sockfd, &msg, 0)) == -1 && + SOCKERRNO == EINTR) + ; + if(nread == -1) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + goto out; + } + if(!cf->connected && SOCKERRNO == ECONNREFUSED) { + const char *r_ip = NULL; + int r_port = 0; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, + &r_ip, &r_port, NULL, NULL); + failf(data, "QUIC: connection to %s port %u refused", + r_ip, r_port); + result = CURLE_COULDNT_CONNECT; + goto out; + } + Curl_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: recvmsg() unexpectedly returned %zd (errno=%d; %s)", + nread, SOCKERRNO, errstr); + result = CURLE_RECV_ERROR; + goto out; + } + + ++pkts; + total_nread += (size_t)nread; + result = recv_cb(buf, (size_t)nread, msg.msg_name, msg.msg_namelen, + 0, userp); + if(result) + goto out; + } + +out: + if(total_nread || result) + CURL_TRC_CF(data, cf, "recvd %zu packets with %zu bytes -> %d", + pkts, total_nread, result); + return result; +} + +#else /* HAVE_SENDMMSG || HAVE_SENDMSG */ +static CURLcode recvfrom_packets(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + size_t max_pkts, + vquic_recv_pkt_cb *recv_cb, void *userp) +{ + uint8_t buf[64*1024]; + int bufsize = (int)sizeof(buf); + struct sockaddr_storage remote_addr; + socklen_t remote_addrlen = sizeof(remote_addr); + size_t total_nread, pkts; + ssize_t nread; + char errstr[STRERROR_LEN]; + CURLcode result = CURLE_OK; + + DEBUGASSERT(max_pkts > 0); + for(pkts = 0, total_nread = 0; pkts < max_pkts;) { + while((nread = recvfrom(qctx->sockfd, (char *)buf, bufsize, 0, + (struct sockaddr *)&remote_addr, + &remote_addrlen)) == -1 && + SOCKERRNO == EINTR) + ; + if(nread == -1) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + CURL_TRC_CF(data, cf, "ingress, recvfrom -> EAGAIN"); + goto out; + } + if(!cf->connected && SOCKERRNO == ECONNREFUSED) { + const char *r_ip = NULL; + int r_port = 0; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, + &r_ip, &r_port, NULL, NULL); + failf(data, "QUIC: connection to %s port %u refused", + r_ip, r_port); + result = CURLE_COULDNT_CONNECT; + goto out; + } + Curl_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: recvfrom() unexpectedly returned %zd (errno=%d; %s)", + nread, SOCKERRNO, errstr); + result = CURLE_RECV_ERROR; + goto out; + } + + ++pkts; + total_nread += (size_t)nread; + result = recv_cb(buf, (size_t)nread, &remote_addr, remote_addrlen, + 0, userp); + if(result) + goto out; + } + +out: + if(total_nread || result) + CURL_TRC_CF(data, cf, "recvd %zu packets with %zu bytes -> %d", + pkts, total_nread, result); + return result; +} +#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_pkt_cb *recv_cb, void *userp) +{ + CURLcode result; +#if defined(HAVE_SENDMMSG) + result = recvmmsg_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 + result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp); +#endif + if(!result) + qctx->last_io = qctx->last_op; + return result; +} + +/* + * If the QLOGDIR environment variable is set, open and return a file + * descriptor to write the log to. + * + * This function returns error if something failed outside of failing to + * create the file. Open file success is deemed by seeing if the returned fd + * is != -1. + */ +CURLcode Curl_qlogdir(struct Curl_easy *data, + unsigned char *scid, + size_t scidlen, + int *qlogfdp) +{ + const char *qlog_dir = getenv("QLOGDIR"); + *qlogfdp = -1; + if(qlog_dir) { + struct dynbuf fname; + CURLcode result; + unsigned int i; + Curl_dyn_init(&fname, DYN_QLOG_NAME); + result = Curl_dyn_add(&fname, qlog_dir); + if(!result) + result = Curl_dyn_add(&fname, "/"); + for(i = 0; (i < scidlen) && !result; i++) { + char hex[3]; + msnprintf(hex, 3, "%02x", scid[i]); + result = Curl_dyn_add(&fname, hex); + } + if(!result) + result = Curl_dyn_add(&fname, ".sqlog"); + + if(!result) { + int qlogfd = open(Curl_dyn_ptr(&fname), QLOGMODE, + data->set.new_file_perms); + if(qlogfd != -1) + *qlogfdp = qlogfd; + } + Curl_dyn_free(&fname); + if(result) + return result; + } + + return CURLE_OK; +} + +CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport) +{ + (void)transport; + DEBUGASSERT(transport == TRNSPRT_QUIC); +#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) + return Curl_cf_ngtcp2_create(pcf, data, conn, ai); +#elif defined(USE_QUICHE) + return Curl_cf_quiche_create(pcf, data, conn, ai); +#elif defined(USE_MSH3) + return Curl_cf_msh3_create(pcf, data, conn, ai); +#else + *pcf = NULL; + (void)data; + (void)conn; + (void)ai; + return CURLE_NOT_BUILT_IN; +#endif +} + +bool Curl_conn_is_http3(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ +#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) + return Curl_conn_is_ngtcp2(data, conn, sockindex); +#elif defined(USE_QUICHE) + return Curl_conn_is_quiche(data, conn, sockindex); +#elif defined(USE_MSH3) + return Curl_conn_is_msh3(data, conn, sockindex); +#else + return ((conn->handler->protocol & PROTO_FAMILY_HTTP) && + (conn->httpversion == 30)); +#endif +} + +CURLcode Curl_conn_may_http3(struct Curl_easy *data, + const struct connectdata *conn) +{ + if(conn->transport == TRNSPRT_UNIX) { + /* cannot do QUIC over a unix domain socket */ + return CURLE_QUIC_CONNECT_ERROR; + } + if(!(conn->handler->flags & PROTOPT_SSL)) { + failf(data, "HTTP/3 requested for non-HTTPS URL"); + return CURLE_URL_MALFORMAT; + } +#ifndef CURL_DISABLE_PROXY + if(conn->bits.socksproxy) { + failf(data, "HTTP/3 is not supported over a SOCKS proxy"); + return CURLE_URL_MALFORMAT; + } + if(conn->bits.httpproxy && conn->bits.tunnel_proxy) { + failf(data, "HTTP/3 is not supported over a HTTP proxy"); + return CURLE_URL_MALFORMAT; + } +#endif + + return CURLE_OK; +} + +#else /* ENABLE_QUIC */ + +CURLcode Curl_conn_may_http3(struct Curl_easy *data, + const struct connectdata *conn) +{ + (void)conn; + (void)data; + DEBUGF(infof(data, "QUIC is not supported in this build")); + return CURLE_NOT_BUILT_IN; +} + +#endif /* !ENABLE_QUIC */ diff --git a/Utilities/cmcurl/lib/vquic/vquic.h b/Utilities/cmcurl/lib/vquic/vquic.h new file mode 100644 index 0000000..dc73957 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/vquic.h @@ -0,0 +1,64 @@ +#ifndef HEADER_CURL_VQUIC_QUIC_H +#define HEADER_CURL_VQUIC_QUIC_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef ENABLE_QUIC +struct Curl_cfilter; +struct Curl_easy; +struct connectdata; +struct Curl_addrinfo; + +void Curl_quic_ver(char *p, size_t len); + +CURLcode Curl_qlogdir(struct Curl_easy *data, + unsigned char *scid, + size_t scidlen, + int *qlogfdp); + + +CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport); + +bool Curl_conn_is_http3(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); + +extern struct Curl_cftype Curl_cft_http3; + +#else /* ENABLE_QUIC */ + +#define Curl_conn_is_http3(a,b,c) FALSE + +#endif /* !ENABLE_QUIC */ + +CURLcode Curl_conn_may_http3(struct Curl_easy *data, + const struct connectdata *conn); + +#endif /* HEADER_CURL_VQUIC_QUIC_H */ diff --git a/Utilities/cmcurl/lib/vquic/vquic_int.h b/Utilities/cmcurl/lib/vquic/vquic_int.h new file mode 100644 index 0000000..a820f39 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/vquic_int.h @@ -0,0 +1,91 @@ +#ifndef HEADER_CURL_VQUIC_QUIC_INT_H +#define HEADER_CURL_VQUIC_QUIC_INT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "bufq.h" + +#ifdef ENABLE_QUIC + +#define MAX_PKT_BURST 10 +#define MAX_UDP_PAYLOAD_SIZE 1452 +/* Default QUIC connection timeout we announce from our side */ +#define CURL_QUIC_MAX_IDLE_MS (120 * 1000) + +struct cf_quic_ctx { + curl_socket_t sockfd; /* connected UDP socket */ + struct sockaddr_storage local_addr; /* address socket is bound to */ + socklen_t local_addrlen; /* length of local address */ + + struct bufq sendbuf; /* buffer for sending one or more packets */ + struct curltime last_op; /* last (attempted) send/recv operation */ + struct curltime last_io; /* last successful socket IO */ + size_t gsolen; /* length of individual packets in send buf */ + size_t split_len; /* if != 0, buffer length after which GSO differs */ + size_t split_gsolen; /* length of individual packets after split_len */ +#ifdef DEBUGBUILD + int wblock_percent; /* percent of writes doing EAGAIN */ +#endif + bool no_gso; /* do not use gso on sending */ +}; + +CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx); +void vquic_ctx_free(struct cf_quic_ctx *qctx); + +void vquic_ctx_update_time(struct cf_quic_ctx *qctx); + +void vquic_push_blocked_pkt(struct Curl_cfilter *cf, + struct cf_quic_ctx *qctx, + const uint8_t *pkt, size_t pktlen, size_t gsolen); + +CURLcode vquic_send_blocked_pkts(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx); + +CURLcode 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 vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, + struct cf_quic_ctx *qctx); + + +typedef CURLcode vquic_recv_pkt_cb(const unsigned char *pkt, size_t pktlen, + struct sockaddr_storage *remote_addr, + socklen_t remote_addrlen, int 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_pkt_cb *recv_cb, void *userp); + +#endif /* !ENABLE_QUIC */ + +#endif /* HEADER_CURL_VQUIC_QUIC_INT_H */ diff --git a/Utilities/cmcurl/lib/vssh/libssh.c b/Utilities/cmcurl/lib/vssh/libssh.c new file mode 100644 index 0000000..97143c4 --- /dev/null +++ b/Utilities/cmcurl/lib/vssh/libssh.c @@ -0,0 +1,2966 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Red Hat, Inc. + * + * Authors: Nikos Mavrogiannopoulos, Tomas Mraz, Stanislav Zidek, + * Robert Kolcun, Andreas Schneider + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_LIBSSH + +#include <limits.h> + +#include <libssh/libssh.h> +#include <libssh/sftp.h> + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include <curl/curl.h> +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "progress.h" +#include "transfer.h" +#include "escape.h" +#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "ssh.h" +#include "url.h" +#include "speedcheck.h" +#include "getinfo.h" +#include "strdup.h" +#include "strcase.h" +#include "vtls/vtls.h" +#include "cfilters.h" +#include "connect.h" +#include "inet_ntop.h" +#include "parsedate.h" /* for the week day and month names */ +#include "sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "strtoofft.h" +#include "multiif.h" +#include "select.h" +#include "warnless.h" +#include "curl_path.h" + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* in 0.10.0 or later, ignore deprecated warnings */ +#if defined(__GNUC__) && \ + (LIBSSH_VERSION_MINOR >= 10) || \ + (LIBSSH_VERSION_MAJOR > 0) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +/* A recent macro provided by libssh. Or make our own. */ +#ifndef SSH_STRING_FREE_CHAR +#define SSH_STRING_FREE_CHAR(x) \ + do { \ + if(x) { \ + ssh_string_free_char(x); \ + x = NULL; \ + } \ + } while(0) +#endif + +/* These stat values may not be the same as the user's S_IFMT / S_IFLNK */ +#ifndef SSH_S_IFMT +#define SSH_S_IFMT 00170000 +#endif +#ifndef SSH_S_IFLNK +#define SSH_S_IFLNK 0120000 +#endif + +/* Local functions: */ +static CURLcode myssh_connect(struct Curl_easy *data, bool *done); +static CURLcode myssh_multi_statemach(struct Curl_easy *data, + bool *done); +static CURLcode myssh_do_it(struct Curl_easy *data, bool *done); + +static CURLcode scp_done(struct Curl_easy *data, + CURLcode, bool premature); +static CURLcode scp_doing(struct Curl_easy *data, bool *dophase_done); +static CURLcode scp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection); + +static CURLcode sftp_done(struct Curl_easy *data, + CURLcode, bool premature); +static CURLcode sftp_doing(struct Curl_easy *data, + bool *dophase_done); +static CURLcode sftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead); +static +CURLcode sftp_perform(struct Curl_easy *data, + bool *connected, + bool *dophase_done); + +static void sftp_quote(struct Curl_easy *data); +static void sftp_quote_stat(struct Curl_easy *data); +static int myssh_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *sock); + +static CURLcode myssh_setup_connection(struct Curl_easy *data, + struct connectdata *conn); + +/* + * SCP protocol handler. + */ + +const struct Curl_handler Curl_handler_scp = { + "SCP", /* scheme */ + myssh_setup_connection, /* setup_connection */ + myssh_do_it, /* do_it */ + scp_done, /* done */ + ZERO_NULL, /* do_more */ + myssh_connect, /* connect_it */ + myssh_multi_statemach, /* connecting */ + scp_doing, /* doing */ + myssh_getsock, /* proto_getsock */ + myssh_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + myssh_getsock, /* perform_getsock */ + scp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_SSH, /* defport */ + CURLPROTO_SCP, /* protocol */ + CURLPROTO_SCP, /* family */ + PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY /* flags */ +}; + +/* + * SFTP protocol handler. + */ + +const struct Curl_handler Curl_handler_sftp = { + "SFTP", /* scheme */ + myssh_setup_connection, /* setup_connection */ + myssh_do_it, /* do_it */ + sftp_done, /* done */ + ZERO_NULL, /* do_more */ + myssh_connect, /* connect_it */ + myssh_multi_statemach, /* connecting */ + sftp_doing, /* doing */ + myssh_getsock, /* proto_getsock */ + myssh_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + myssh_getsock, /* perform_getsock */ + sftp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_SSH, /* defport */ + CURLPROTO_SFTP, /* protocol */ + CURLPROTO_SFTP, /* family */ + PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION + | PROTOPT_NOURLQUERY /* flags */ +}; + +static CURLcode sftp_error_to_CURLE(int err) +{ + switch(err) { + case SSH_FX_OK: + return CURLE_OK; + + case SSH_FX_NO_SUCH_FILE: + case SSH_FX_NO_SUCH_PATH: + return CURLE_REMOTE_FILE_NOT_FOUND; + + case SSH_FX_PERMISSION_DENIED: + case SSH_FX_WRITE_PROTECT: + return CURLE_REMOTE_ACCESS_DENIED; + + case SSH_FX_FILE_ALREADY_EXISTS: + return CURLE_REMOTE_FILE_EXISTS; + + default: + break; + } + + return CURLE_SSH; +} + +#ifndef DEBUGBUILD +#define state(x,y) mystate(x,y) +#else +#define state(x,y) mystate(x,y, __LINE__) +#endif + +/* + * SSH State machine related code + */ +/* This is the ONLY way to change SSH state! */ +static void mystate(struct Curl_easy *data, sshstate nowstate +#ifdef DEBUGBUILD + , int lineno +#endif + ) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char *const names[] = { + "SSH_STOP", + "SSH_INIT", + "SSH_S_STARTUP", + "SSH_HOSTKEY", + "SSH_AUTHLIST", + "SSH_AUTH_PKEY_INIT", + "SSH_AUTH_PKEY", + "SSH_AUTH_PASS_INIT", + "SSH_AUTH_PASS", + "SSH_AUTH_AGENT_INIT", + "SSH_AUTH_AGENT_LIST", + "SSH_AUTH_AGENT", + "SSH_AUTH_HOST_INIT", + "SSH_AUTH_HOST", + "SSH_AUTH_KEY_INIT", + "SSH_AUTH_KEY", + "SSH_AUTH_GSSAPI", + "SSH_AUTH_DONE", + "SSH_SFTP_INIT", + "SSH_SFTP_REALPATH", + "SSH_SFTP_QUOTE_INIT", + "SSH_SFTP_POSTQUOTE_INIT", + "SSH_SFTP_QUOTE", + "SSH_SFTP_NEXT_QUOTE", + "SSH_SFTP_QUOTE_STAT", + "SSH_SFTP_QUOTE_SETSTAT", + "SSH_SFTP_QUOTE_SYMLINK", + "SSH_SFTP_QUOTE_MKDIR", + "SSH_SFTP_QUOTE_RENAME", + "SSH_SFTP_QUOTE_RMDIR", + "SSH_SFTP_QUOTE_UNLINK", + "SSH_SFTP_QUOTE_STATVFS", + "SSH_SFTP_GETINFO", + "SSH_SFTP_FILETIME", + "SSH_SFTP_TRANS_INIT", + "SSH_SFTP_UPLOAD_INIT", + "SSH_SFTP_CREATE_DIRS_INIT", + "SSH_SFTP_CREATE_DIRS", + "SSH_SFTP_CREATE_DIRS_MKDIR", + "SSH_SFTP_READDIR_INIT", + "SSH_SFTP_READDIR", + "SSH_SFTP_READDIR_LINK", + "SSH_SFTP_READDIR_BOTTOM", + "SSH_SFTP_READDIR_DONE", + "SSH_SFTP_DOWNLOAD_INIT", + "SSH_SFTP_DOWNLOAD_STAT", + "SSH_SFTP_CLOSE", + "SSH_SFTP_SHUTDOWN", + "SSH_SCP_TRANS_INIT", + "SSH_SCP_UPLOAD_INIT", + "SSH_SCP_DOWNLOAD_INIT", + "SSH_SCP_DOWNLOAD", + "SSH_SCP_DONE", + "SSH_SCP_SEND_EOF", + "SSH_SCP_WAIT_EOF", + "SSH_SCP_WAIT_CLOSE", + "SSH_SCP_CHANNEL_FREE", + "SSH_SESSION_DISCONNECT", + "SSH_SESSION_FREE", + "QUIT" + }; + + + if(sshc->state != nowstate) { + infof(data, "SSH %p state change from %s to %s (line %d)", + (void *) sshc, names[sshc->state], names[nowstate], + lineno); + } +#endif + + sshc->state = nowstate; +} + +/* Multiple options: + * 1. 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) + * 2. 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. + * 3. none of the above. We only accept if it is present on known hosts. + * + * Returns SSH_OK or SSH_ERROR. + */ +static int myssh_is_known(struct Curl_easy *data) +{ + int rc; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + ssh_key pubkey; + size_t hlen; + unsigned char *hash = NULL; + char *found_base64 = NULL; + char *known_base64 = NULL; + int vstate; + enum curl_khmatch keymatch; + struct curl_khkey foundkey; + struct curl_khkey *knownkeyp = NULL; + curl_sshkeycallback func = + data->set.ssh_keyfunc; + +#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) + struct ssh_knownhosts_entry *knownhostsentry = NULL; + struct curl_khkey knownkey; +#endif + +#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,8,0) + rc = ssh_get_server_publickey(sshc->ssh_session, &pubkey); +#else + rc = ssh_get_publickey(sshc->ssh_session, &pubkey); +#endif + if(rc != SSH_OK) + return rc; + + if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) { + int i; + char md5buffer[33]; + const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, + &hash, &hlen); + if(rc != SSH_OK || hlen != 16) { + failf(data, + "Denied establishing ssh session: md5 fingerprint not available"); + goto cleanup; + } + + for(i = 0; i < 16; i++) + msnprintf(&md5buffer[i*2], 3, "%02x", (unsigned char)hash[i]); + + infof(data, "SSH MD5 fingerprint: %s", md5buffer); + + if(!strcasecompare(md5buffer, pubkey_md5)) { + failf(data, + "Denied establishing ssh session: mismatch md5 fingerprint. " + "Remote %s is not equal to %s", md5buffer, pubkey_md5); + rc = SSH_ERROR; + goto cleanup; + } + + rc = SSH_OK; + goto cleanup; + } + + if(data->set.ssl.primary.verifyhost != TRUE) { + rc = SSH_OK; + goto cleanup; + } + +#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) + /* Get the known_key from the known hosts file */ + vstate = ssh_session_get_known_hosts_entry(sshc->ssh_session, + &knownhostsentry); + + /* Case an entry was found in a known hosts file */ + if(knownhostsentry) { + if(knownhostsentry->publickey) { + rc = ssh_pki_export_pubkey_base64(knownhostsentry->publickey, + &known_base64); + if(rc != SSH_OK) { + goto cleanup; + } + knownkey.key = known_base64; + knownkey.len = strlen(known_base64); + + switch(ssh_key_type(knownhostsentry->publickey)) { + case SSH_KEYTYPE_RSA: + knownkey.keytype = CURLKHTYPE_RSA; + break; + case SSH_KEYTYPE_RSA1: + knownkey.keytype = CURLKHTYPE_RSA1; + break; + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + knownkey.keytype = CURLKHTYPE_ECDSA; + break; + case SSH_KEYTYPE_ED25519: + knownkey.keytype = CURLKHTYPE_ED25519; + break; + case SSH_KEYTYPE_DSS: + knownkey.keytype = CURLKHTYPE_DSS; + break; + default: + rc = SSH_ERROR; + goto cleanup; + } + knownkeyp = &knownkey; + } + } + + switch(vstate) { + case SSH_KNOWN_HOSTS_OK: + keymatch = CURLKHMATCH_OK; + break; + case SSH_KNOWN_HOSTS_OTHER: + /* fallthrough */ + case SSH_KNOWN_HOSTS_NOT_FOUND: + /* fallthrough */ + case SSH_KNOWN_HOSTS_UNKNOWN: + /* fallthrough */ + case SSH_KNOWN_HOSTS_ERROR: + keymatch = CURLKHMATCH_MISSING; + break; + default: + keymatch = CURLKHMATCH_MISMATCH; + break; + } + +#else + vstate = ssh_is_server_known(sshc->ssh_session); + switch(vstate) { + case SSH_SERVER_KNOWN_OK: + keymatch = CURLKHMATCH_OK; + break; + case SSH_SERVER_FILE_NOT_FOUND: + /* fallthrough */ + case SSH_SERVER_NOT_KNOWN: + keymatch = CURLKHMATCH_MISSING; + break; + default: + keymatch = CURLKHMATCH_MISMATCH; + break; + } +#endif + + if(func) { /* use callback to determine action */ + rc = ssh_pki_export_pubkey_base64(pubkey, &found_base64); + if(rc != SSH_OK) + goto cleanup; + + foundkey.key = found_base64; + foundkey.len = strlen(found_base64); + + switch(ssh_key_type(pubkey)) { + case SSH_KEYTYPE_RSA: + foundkey.keytype = CURLKHTYPE_RSA; + break; + case SSH_KEYTYPE_RSA1: + foundkey.keytype = CURLKHTYPE_RSA1; + break; + case SSH_KEYTYPE_ECDSA: +#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#endif + foundkey.keytype = CURLKHTYPE_ECDSA; + break; +#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,7,0) + case SSH_KEYTYPE_ED25519: + foundkey.keytype = CURLKHTYPE_ED25519; + break; +#endif + case SSH_KEYTYPE_DSS: + foundkey.keytype = CURLKHTYPE_DSS; + break; + default: + rc = SSH_ERROR; + goto cleanup; + } + + Curl_set_in_callback(data, true); + 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); + + switch(rc) { + case CURLKHSTAT_FINE_ADD_TO_FILE: +#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,8,0) + rc = ssh_session_update_known_hosts(sshc->ssh_session); +#else + rc = ssh_write_knownhost(sshc->ssh_session); +#endif + if(rc != SSH_OK) { + goto cleanup; + } + break; + case CURLKHSTAT_FINE: + break; + default: /* REJECT/DEFER */ + rc = SSH_ERROR; + goto cleanup; + } + } + else { + if(keymatch != CURLKHMATCH_OK) { + rc = SSH_ERROR; + goto cleanup; + } + } + rc = SSH_OK; + +cleanup: + if(found_base64) { + (free)(found_base64); + } + if(known_base64) { + (free)(known_base64); + } + if(hash) + ssh_clean_pubkey_hash(&hash); + ssh_key_free(pubkey); +#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) + if(knownhostsentry) { + ssh_knownhosts_entry_free(knownhostsentry); + } +#endif + return rc; +} + +#define MOVE_TO_ERROR_STATE(_r) do { \ + state(data, SSH_SESSION_DISCONNECT); \ + sshc->actualcode = _r; \ + rc = SSH_ERROR; \ + } while(0) + +#define MOVE_TO_SFTP_CLOSE_STATE() do { \ + state(data, SSH_SFTP_CLOSE); \ + sshc->actualcode = \ + sftp_error_to_CURLE(sftp_get_error(sshc->sftp_session)); \ + rc = SSH_ERROR; \ + } while(0) + +#define MOVE_TO_PASSWD_AUTH do { \ + if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) { \ + rc = SSH_OK; \ + state(data, SSH_AUTH_PASS_INIT); \ + } \ + else { \ + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); \ + } \ + } while(0) + +#define MOVE_TO_KEY_AUTH do { \ + if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { \ + rc = SSH_OK; \ + state(data, SSH_AUTH_KEY_INIT); \ + } \ + else { \ + MOVE_TO_PASSWD_AUTH; \ + } \ + } while(0) + +#define MOVE_TO_GSSAPI_AUTH do { \ + if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { \ + rc = SSH_OK; \ + state(data, SSH_AUTH_GSSAPI); \ + } \ + else { \ + MOVE_TO_KEY_AUTH; \ + } \ + } while(0) + +static +int myssh_auth_interactive(struct connectdata *conn) +{ + int rc; + struct ssh_conn *sshc = &conn->proto.sshc; + int nprompts; + +restart: + switch(sshc->kbd_state) { + case 0: + rc = ssh_userauth_kbdint(sshc->ssh_session, NULL, NULL); + if(rc == SSH_AUTH_AGAIN) + return SSH_AGAIN; + + if(rc != SSH_AUTH_INFO) + return SSH_ERROR; + + nprompts = ssh_userauth_kbdint_getnprompts(sshc->ssh_session); + if(nprompts != 1) + return SSH_ERROR; + + rc = ssh_userauth_kbdint_setanswer(sshc->ssh_session, 0, conn->passwd); + if(rc < 0) + return SSH_ERROR; + + /* FALLTHROUGH */ + case 1: + sshc->kbd_state = 1; + + rc = ssh_userauth_kbdint(sshc->ssh_session, NULL, NULL); + if(rc == SSH_AUTH_AGAIN) + return SSH_AGAIN; + else if(rc == SSH_AUTH_SUCCESS) + rc = SSH_OK; + else if(rc == SSH_AUTH_INFO) { + nprompts = ssh_userauth_kbdint_getnprompts(sshc->ssh_session); + if(nprompts) + return SSH_ERROR; + + sshc->kbd_state = 2; + goto restart; + } + else + rc = SSH_ERROR; + break; + case 2: + sshc->kbd_state = 2; + + rc = ssh_userauth_kbdint(sshc->ssh_session, NULL, NULL); + if(rc == SSH_AUTH_AGAIN) + return SSH_AGAIN; + else if(rc == SSH_AUTH_SUCCESS) + rc = SSH_OK; + else + rc = SSH_ERROR; + + break; + default: + return SSH_ERROR; + } + + sshc->kbd_state = 0; + return rc; +} + +/* + * ssh_statemach_act() runs the SSH state machine as far as it can without + * blocking and without reaching the end. The data the pointer 'block' points + * to will be set to TRUE if the libssh function returns SSH_AGAIN + * meaning it wants to be called again when the socket is ready + */ +static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct SSHPROTO *protop = data->req.p.ssh; + struct ssh_conn *sshc = &conn->proto.sshc; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int rc = SSH_NO_ERROR, err; + int seekerr = CURL_SEEKFUNC_OK; + const char *err_msg; + *block = 0; /* we're not blocking by default */ + + do { + + switch(sshc->state) { + case SSH_INIT: + sshc->secondCreateDirs = 0; + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_OK; + +#if 0 + ssh_set_log_level(SSH_LOG_PROTOCOL); +#endif + + /* Set libssh to non-blocking, since everything internally is + non-blocking */ + ssh_set_blocking(sshc->ssh_session, 0); + + state(data, SSH_S_STARTUP); + /* FALLTHROUGH */ + + case SSH_S_STARTUP: + rc = ssh_connect(sshc->ssh_session); + if(rc == SSH_AGAIN) + break; + + if(rc != SSH_OK) { + failf(data, "Failure establishing ssh session"); + MOVE_TO_ERROR_STATE(CURLE_FAILED_INIT); + break; + } + + state(data, SSH_HOSTKEY); + + /* FALLTHROUGH */ + case SSH_HOSTKEY: + + rc = myssh_is_known(data); + if(rc != SSH_OK) { + MOVE_TO_ERROR_STATE(CURLE_PEER_FAILED_VERIFICATION); + break; + } + + state(data, SSH_AUTHLIST); + /* FALLTHROUGH */ + case SSH_AUTHLIST:{ + sshc->authed = FALSE; + + rc = ssh_userauth_none(sshc->ssh_session, NULL); + if(rc == SSH_AUTH_AGAIN) { + rc = SSH_AGAIN; + break; + } + + if(rc == SSH_AUTH_SUCCESS) { + sshc->authed = TRUE; + infof(data, "Authenticated with none"); + state(data, SSH_AUTH_DONE); + break; + } + else if(rc == SSH_AUTH_ERROR) { + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + break; + } + + sshc->auth_methods = ssh_userauth_list(sshc->ssh_session, NULL); + if(sshc->auth_methods) + infof(data, "SSH authentication methods available: %s%s%s%s", + sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY ? + "public key, ": "", + sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC ? + "GSSAPI, " : "", + sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE ? + "keyboard-interactive, " : "", + sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD ? + "password": ""); + if(sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { + state(data, SSH_AUTH_PKEY_INIT); + infof(data, "Authentication using SSH public key file"); + } + else if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { + state(data, SSH_AUTH_GSSAPI); + } + else if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { + state(data, SSH_AUTH_KEY_INIT); + } + else if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) { + state(data, SSH_AUTH_PASS_INIT); + } + else { /* unsupported authentication method */ + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + break; + } + + break; + } + case SSH_AUTH_PKEY_INIT: + if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY)) { + MOVE_TO_GSSAPI_AUTH; + break; + } + + /* Two choices, (1) private key was given on CMD, + * (2) use the "default" keys. */ + if(data->set.str[STRING_SSH_PRIVATE_KEY]) { + if(sshc->pubkey && !data->set.ssl.key_passwd) { + rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, + sshc->pubkey); + if(rc == SSH_AUTH_AGAIN) { + rc = SSH_AGAIN; + break; + } + + if(rc != SSH_OK) { + MOVE_TO_GSSAPI_AUTH; + break; + } + } + + rc = ssh_pki_import_privkey_file(data-> + set.str[STRING_SSH_PRIVATE_KEY], + data->set.ssl.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]); + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + break; + } + + state(data, SSH_AUTH_PKEY); + break; + + } + else { + rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, + data->set.ssl.key_passwd); + if(rc == SSH_AUTH_AGAIN) { + rc = SSH_AGAIN; + break; + } + if(rc == SSH_AUTH_SUCCESS) { + rc = SSH_OK; + sshc->authed = TRUE; + infof(data, "Completed public key authentication"); + state(data, SSH_AUTH_DONE); + break; + } + + MOVE_TO_GSSAPI_AUTH; + } + break; + case SSH_AUTH_PKEY: + rc = ssh_userauth_publickey(sshc->ssh_session, NULL, sshc->privkey); + if(rc == SSH_AUTH_AGAIN) { + rc = SSH_AGAIN; + break; + } + + if(rc == SSH_AUTH_SUCCESS) { + sshc->authed = TRUE; + infof(data, "Completed public key authentication"); + state(data, SSH_AUTH_DONE); + break; + } + else { + infof(data, "Failed public key authentication (rc: %d)", rc); + MOVE_TO_GSSAPI_AUTH; + } + break; + + case SSH_AUTH_GSSAPI: + if(!(data->set.ssh_auth_types & CURLSSH_AUTH_GSSAPI)) { + MOVE_TO_KEY_AUTH; + break; + } + + rc = ssh_userauth_gssapi(sshc->ssh_session); + if(rc == SSH_AUTH_AGAIN) { + rc = SSH_AGAIN; + break; + } + + if(rc == SSH_AUTH_SUCCESS) { + rc = SSH_OK; + sshc->authed = TRUE; + infof(data, "Completed gssapi authentication"); + state(data, SSH_AUTH_DONE); + break; + } + + MOVE_TO_KEY_AUTH; + break; + + case SSH_AUTH_KEY_INIT: + if(data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) { + state(data, SSH_AUTH_KEY); + } + else { + MOVE_TO_PASSWD_AUTH; + } + break; + + case SSH_AUTH_KEY: + /* keyboard-interactive authentication */ + rc = myssh_auth_interactive(conn); + if(rc == SSH_AGAIN) { + break; + } + if(rc == SSH_OK) { + sshc->authed = TRUE; + infof(data, "completed keyboard interactive authentication"); + state(data, SSH_AUTH_DONE); + } + else { + MOVE_TO_PASSWD_AUTH; + } + break; + + case SSH_AUTH_PASS_INIT: + if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD)) { + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + break; + } + state(data, SSH_AUTH_PASS); + /* FALLTHROUGH */ + + case SSH_AUTH_PASS: + rc = ssh_userauth_password(sshc->ssh_session, NULL, conn->passwd); + if(rc == SSH_AUTH_AGAIN) { + rc = SSH_AGAIN; + break; + } + + if(rc == SSH_AUTH_SUCCESS) { + sshc->authed = TRUE; + infof(data, "Completed password authentication"); + state(data, SSH_AUTH_DONE); + } + else { + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + } + break; + + case SSH_AUTH_DONE: + if(!sshc->authed) { + failf(data, "Authentication failure"); + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + break; + } + + /* + * At this point we have an authenticated ssh session. + */ + infof(data, "Authentication complete"); + + Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSH is connected */ + + conn->sockfd = sock; + conn->writesockfd = CURL_SOCKET_BAD; + + if(conn->handler->protocol == CURLPROTO_SFTP) { + state(data, SSH_SFTP_INIT); + break; + } + infof(data, "SSH CONNECT phase done"); + state(data, SSH_STOP); + break; + + case SSH_SFTP_INIT: + ssh_set_blocking(sshc->ssh_session, 1); + + sshc->sftp_session = sftp_new(sshc->ssh_session); + if(!sshc->sftp_session) { + failf(data, "Failure initializing sftp session: %s", + ssh_get_error(sshc->ssh_session)); + MOVE_TO_ERROR_STATE(CURLE_COULDNT_CONNECT); + break; + } + + rc = sftp_init(sshc->sftp_session); + if(rc != SSH_OK) { + failf(data, "Failure initializing sftp session: %s", + ssh_get_error(sshc->ssh_session)); + MOVE_TO_ERROR_STATE(sftp_error_to_CURLE(SSH_FX_FAILURE)); + break; + } + state(data, SSH_SFTP_REALPATH); + /* FALLTHROUGH */ + case SSH_SFTP_REALPATH: + /* + * Get the "home" directory + */ + sshc->homedir = sftp_canonicalize_path(sshc->sftp_session, "."); + if(!sshc->homedir) { + MOVE_TO_ERROR_STATE(CURLE_COULDNT_CONNECT); + break; + } + data->state.most_recent_ftp_entrypath = sshc->homedir; + + /* This is the last step in the SFTP connect phase. Do note that while + we get the homedir here, we get the "workingpath" in the DO action + since the homedir will remain the same between request but the + working path will not. */ + DEBUGF(infof(data, "SSH CONNECT phase done")); + state(data, SSH_STOP); + break; + + case SSH_SFTP_QUOTE_INIT: + result = Curl_getworkingpath(data, sshc->homedir, &protop->path); + if(result) { + sshc->actualcode = result; + state(data, SSH_STOP); + break; + } + + if(data->set.quote) { + infof(data, "Sending quote commands"); + sshc->quote_item = data->set.quote; + state(data, SSH_SFTP_QUOTE); + } + else { + state(data, SSH_SFTP_GETINFO); + } + break; + + case SSH_SFTP_POSTQUOTE_INIT: + if(data->set.postquote) { + infof(data, "Sending quote commands"); + sshc->quote_item = data->set.postquote; + state(data, SSH_SFTP_QUOTE); + } + else { + state(data, SSH_STOP); + } + break; + + case SSH_SFTP_QUOTE: + /* Send any quote commands */ + sftp_quote(data); + break; + + case SSH_SFTP_NEXT_QUOTE: + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + + sshc->quote_item = sshc->quote_item->next; + + if(sshc->quote_item) { + state(data, SSH_SFTP_QUOTE); + } + else { + if(sshc->nextstate != SSH_NO_STATE) { + state(data, sshc->nextstate); + sshc->nextstate = SSH_NO_STATE; + } + else { + state(data, SSH_SFTP_GETINFO); + } + } + break; + + case SSH_SFTP_QUOTE_STAT: + sftp_quote_stat(data); + break; + + case SSH_SFTP_QUOTE_SETSTAT: + rc = sftp_setstat(sshc->sftp_session, sshc->quote_path2, + sshc->quote_attrs); + if(rc && !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Attempt to set SFTP stats failed: %s", + ssh_get_error(sshc->ssh_session)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + /* sshc->actualcode = sftp_error_to_CURLE(err); + * we do not send the actual error; we return + * the error the libssh2 backend is returning */ + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_SYMLINK: + rc = sftp_symlink(sshc->sftp_session, sshc->quote_path2, + sshc->quote_path1); + if(rc && !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "symlink command failed: %s", + ssh_get_error(sshc->ssh_session)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_MKDIR: + rc = sftp_mkdir(sshc->sftp_session, sshc->quote_path1, + (mode_t)data->set.new_directory_perms); + if(rc && !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + failf(data, "mkdir command failed: %s", + ssh_get_error(sshc->ssh_session)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_RENAME: + rc = sftp_rename(sshc->sftp_session, sshc->quote_path1, + sshc->quote_path2); + if(rc && !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "rename command failed: %s", + ssh_get_error(sshc->ssh_session)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_RMDIR: + rc = sftp_rmdir(sshc->sftp_session, sshc->quote_path1); + if(rc && !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + failf(data, "rmdir command failed: %s", + ssh_get_error(sshc->ssh_session)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_UNLINK: + rc = sftp_unlink(sshc->sftp_session, sshc->quote_path1); + if(rc && !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + failf(data, "rm command failed: %s", + ssh_get_error(sshc->ssh_session)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_STATVFS: + { + sftp_statvfs_t statvfs; + + statvfs = sftp_statvfs(sshc->sftp_session, sshc->quote_path1); + if(!statvfs && !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + failf(data, "statvfs command failed: %s", + ssh_get_error(sshc->ssh_session)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + else if(statvfs) { + #ifdef _MSC_VER + #define LIBSSH_VFS_SIZE_MASK "I64u" + #else + #define LIBSSH_VFS_SIZE_MASK PRIu64 + #endif + char *tmp = aprintf("statvfs:\n" + "f_bsize: %" LIBSSH_VFS_SIZE_MASK "\n" + "f_frsize: %" LIBSSH_VFS_SIZE_MASK "\n" + "f_blocks: %" LIBSSH_VFS_SIZE_MASK "\n" + "f_bfree: %" LIBSSH_VFS_SIZE_MASK "\n" + "f_bavail: %" LIBSSH_VFS_SIZE_MASK "\n" + "f_files: %" LIBSSH_VFS_SIZE_MASK "\n" + "f_ffree: %" LIBSSH_VFS_SIZE_MASK "\n" + "f_favail: %" LIBSSH_VFS_SIZE_MASK "\n" + "f_fsid: %" LIBSSH_VFS_SIZE_MASK "\n" + "f_flag: %" LIBSSH_VFS_SIZE_MASK "\n" + "f_namemax: %" LIBSSH_VFS_SIZE_MASK "\n", + statvfs->f_bsize, statvfs->f_frsize, + statvfs->f_blocks, statvfs->f_bfree, + statvfs->f_bavail, statvfs->f_files, + statvfs->f_ffree, statvfs->f_favail, + statvfs->f_fsid, statvfs->f_flag, + statvfs->f_namemax); + sftp_statvfs_free(statvfs); + + if(!tmp) { + result = CURLE_OUT_OF_MEMORY; + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + break; + } + + result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); + free(tmp); + if(result) { + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + } + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + } + + case SSH_SFTP_GETINFO: + if(data->set.get_filetime) { + state(data, SSH_SFTP_FILETIME); + } + else { + state(data, SSH_SFTP_TRANS_INIT); + } + break; + + case SSH_SFTP_FILETIME: + { + sftp_attributes attrs; + + attrs = sftp_stat(sshc->sftp_session, protop->path); + if(attrs) { + data->info.filetime = attrs->mtime; + sftp_attributes_free(attrs); + } + + state(data, SSH_SFTP_TRANS_INIT); + break; + } + + case SSH_SFTP_TRANS_INIT: + if(data->state.upload) + state(data, SSH_SFTP_UPLOAD_INIT); + else { + if(protop->path[strlen(protop->path)-1] == '/') + state(data, SSH_SFTP_READDIR_INIT); + else + state(data, SSH_SFTP_DOWNLOAD_INIT); + } + break; + + case SSH_SFTP_UPLOAD_INIT: + { + int flags; + + if(data->state.resume_from) { + sftp_attributes attrs; + + if(data->state.resume_from < 0) { + attrs = sftp_stat(sshc->sftp_session, protop->path); + if(attrs) { + curl_off_t size = attrs->size; + if(size < 0) { + failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); + MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); + break; + } + data->state.resume_from = attrs->size; + + sftp_attributes_free(attrs); + } + else { + data->state.resume_from = 0; + } + } + } + + if(data->set.remote_append) + /* Try to open for append, but create if nonexisting */ + flags = O_WRONLY|O_CREAT|O_APPEND; + else if(data->state.resume_from > 0) + /* If we have restart position then open for append */ + flags = O_WRONLY|O_APPEND; + else + /* Clear file before writing (normal behavior) */ + flags = O_WRONLY|O_CREAT|O_TRUNC; + + if(sshc->sftp_file) + sftp_close(sshc->sftp_file); + sshc->sftp_file = + sftp_open(sshc->sftp_session, protop->path, + flags, (mode_t)data->set.new_file_perms); + if(!sshc->sftp_file) { + err = sftp_get_error(sshc->sftp_session); + + if(((err == SSH_FX_NO_SUCH_FILE || err == SSH_FX_FAILURE || + err == SSH_FX_NO_SUCH_PATH)) && + (data->set.ftp_create_missing_dirs && + (strlen(protop->path) > 1))) { + /* try to create the path remotely */ + rc = 0; + sshc->secondCreateDirs = 1; + state(data, SSH_SFTP_CREATE_DIRS_INIT); + break; + } + else { + MOVE_TO_SFTP_CLOSE_STATE(); + break; + } + } + + /* If we have a restart point then we need to seek to the correct + position. */ + if(data->state.resume_from > 0) { + /* Let's read off the proper amount of bytes from the input. */ + if(conn->seek_func) { + Curl_set_in_callback(data, true); + seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, + SEEK_SET); + Curl_set_in_callback(data, false); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; + + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_FTP_COULDNT_USE_REST; + } + /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + do { + size_t readthisamountnow = + (data->state.resume_from - passed > data->set.buffer_size) ? + (size_t)data->set.buffer_size : + curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread = + data->state.fread_func(data->state.buffer, 1, + readthisamountnow, data->state.in); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Failed to read data"); + MOVE_TO_ERROR_STATE(CURLE_FTP_COULDNT_USE_REST); + break; + } + } while(passed < data->state.resume_from); + if(rc) + break; + } + + /* now, decrease the size of the read */ + if(data->state.infilesize > 0) { + data->state.infilesize -= data->state.resume_from; + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + + rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); + if(rc) { + MOVE_TO_SFTP_CLOSE_STATE(); + break; + } + } + if(data->state.infilesize > 0) { + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + /* upload data */ + Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->sockfd = conn->writesockfd; + + /* store this original bitmask setup to use later on if we can't + figure out a "real" bitmask */ + sshc->orig_waitfor = data->req.keepon; + + /* we want to use the _sending_ function even when the socket turns + out readable as the underlying libssh sftp send function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_OUT; + + /* since we don't really wait for anything at this point, we want the + state machine to move on as soon as possible so we set a very short + timeout here */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + + state(data, SSH_STOP); + break; + } + + case SSH_SFTP_CREATE_DIRS_INIT: + if(strlen(protop->path) > 1) { + sshc->slash_pos = protop->path + 1; /* ignore the leading '/' */ + state(data, SSH_SFTP_CREATE_DIRS); + } + else { + state(data, SSH_SFTP_UPLOAD_INIT); + } + break; + + case SSH_SFTP_CREATE_DIRS: + sshc->slash_pos = strchr(sshc->slash_pos, '/'); + if(sshc->slash_pos) { + *sshc->slash_pos = 0; + + infof(data, "Creating directory '%s'", protop->path); + state(data, SSH_SFTP_CREATE_DIRS_MKDIR); + break; + } + state(data, SSH_SFTP_UPLOAD_INIT); + break; + + case SSH_SFTP_CREATE_DIRS_MKDIR: + /* 'mode' - parameter is preliminary - default to 0644 */ + rc = sftp_mkdir(sshc->sftp_session, protop->path, + (mode_t)data->set.new_directory_perms); + *sshc->slash_pos = '/'; + ++sshc->slash_pos; + if(rc < 0) { + /* + * Abort if failure wasn't that the dir already exists or the + * permission was denied (creation might succeed further down the + * path) - retry on unspecific FAILURE also + */ + err = sftp_get_error(sshc->sftp_session); + if((err != SSH_FX_FILE_ALREADY_EXISTS) && + (err != SSH_FX_FAILURE) && + (err != SSH_FX_PERMISSION_DENIED)) { + MOVE_TO_SFTP_CLOSE_STATE(); + break; + } + rc = 0; /* clear rc and continue */ + } + state(data, SSH_SFTP_CREATE_DIRS); + break; + + case SSH_SFTP_READDIR_INIT: + Curl_pgrsSetDownloadSize(data, -1); + if(data->req.no_body) { + state(data, SSH_STOP); + break; + } + + /* + * This is a directory that we are trying to get, so produce a directory + * listing + */ + sshc->sftp_dir = sftp_opendir(sshc->sftp_session, + protop->path); + if(!sshc->sftp_dir) { + failf(data, "Could not open directory for reading: %s", + ssh_get_error(sshc->ssh_session)); + MOVE_TO_SFTP_CLOSE_STATE(); + break; + } + state(data, SSH_SFTP_READDIR); + break; + + case SSH_SFTP_READDIR: + Curl_dyn_reset(&sshc->readdir_buf); + if(sshc->readdir_attrs) + sftp_attributes_free(sshc->readdir_attrs); + + sshc->readdir_attrs = sftp_readdir(sshc->sftp_session, sshc->sftp_dir); + if(sshc->readdir_attrs) { + sshc->readdir_filename = sshc->readdir_attrs->name; + sshc->readdir_longentry = sshc->readdir_attrs->longname; + sshc->readdir_len = strlen(sshc->readdir_filename); + + if(data->set.list_only) { + char *tmpLine; + + tmpLine = aprintf("%s\n", sshc->readdir_filename); + if(!tmpLine) { + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + result = Curl_client_write(data, CLIENTWRITE_BODY, + tmpLine, sshc->readdir_len + 1); + free(tmpLine); + + if(result) { + state(data, SSH_STOP); + break; + } + + } + else { + if(Curl_dyn_add(&sshc->readdir_buf, sshc->readdir_longentry)) { + sshc->actualcode = CURLE_OUT_OF_MEMORY; + state(data, SSH_STOP); + break; + } + + if((sshc->readdir_attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS) && + ((sshc->readdir_attrs->permissions & SSH_S_IFMT) == + SSH_S_IFLNK)) { + sshc->readdir_linkPath = aprintf("%s%s", protop->path, + sshc->readdir_filename); + + if(!sshc->readdir_linkPath) { + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + + state(data, SSH_SFTP_READDIR_LINK); + break; + } + state(data, SSH_SFTP_READDIR_BOTTOM); + break; + } + } + else if(sftp_dir_eof(sshc->sftp_dir)) { + state(data, SSH_SFTP_READDIR_DONE); + break; + } + else { + failf(data, "Could not open remote file for reading: %s", + ssh_get_error(sshc->ssh_session)); + MOVE_TO_SFTP_CLOSE_STATE(); + break; + } + break; + + case SSH_SFTP_READDIR_LINK: + if(sshc->readdir_link_attrs) + sftp_attributes_free(sshc->readdir_link_attrs); + + sshc->readdir_link_attrs = sftp_lstat(sshc->sftp_session, + sshc->readdir_linkPath); + if(sshc->readdir_link_attrs == 0) { + failf(data, "Could not read symlink for reading: %s", + ssh_get_error(sshc->ssh_session)); + MOVE_TO_SFTP_CLOSE_STATE(); + break; + } + + if(!sshc->readdir_link_attrs->name) { + sshc->readdir_tmp = sftp_readlink(sshc->sftp_session, + sshc->readdir_linkPath); + if(!sshc->readdir_filename) + sshc->readdir_len = 0; + else + sshc->readdir_len = strlen(sshc->readdir_tmp); + sshc->readdir_longentry = NULL; + sshc->readdir_filename = sshc->readdir_tmp; + } + else { + sshc->readdir_len = strlen(sshc->readdir_link_attrs->name); + sshc->readdir_filename = sshc->readdir_link_attrs->name; + sshc->readdir_longentry = sshc->readdir_link_attrs->longname; + } + + Curl_safefree(sshc->readdir_linkPath); + + if(Curl_dyn_addf(&sshc->readdir_buf, " -> %s", + sshc->readdir_filename)) { + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + + sftp_attributes_free(sshc->readdir_link_attrs); + sshc->readdir_link_attrs = NULL; + sshc->readdir_filename = NULL; + sshc->readdir_longentry = NULL; + + state(data, SSH_SFTP_READDIR_BOTTOM); + /* FALLTHROUGH */ + case SSH_SFTP_READDIR_BOTTOM: + if(Curl_dyn_addn(&sshc->readdir_buf, "\n", 1)) + result = CURLE_OUT_OF_MEMORY; + else + result = Curl_client_write(data, CLIENTWRITE_BODY, + Curl_dyn_ptr(&sshc->readdir_buf), + Curl_dyn_len(&sshc->readdir_buf)); + + ssh_string_free_char(sshc->readdir_tmp); + sshc->readdir_tmp = NULL; + + if(result) { + state(data, SSH_STOP); + } + else + state(data, SSH_SFTP_READDIR); + break; + + case SSH_SFTP_READDIR_DONE: + sftp_closedir(sshc->sftp_dir); + sshc->sftp_dir = NULL; + + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + state(data, SSH_STOP); + break; + + case SSH_SFTP_DOWNLOAD_INIT: + /* + * Work on getting the specified file + */ + if(sshc->sftp_file) + sftp_close(sshc->sftp_file); + + sshc->sftp_file = sftp_open(sshc->sftp_session, protop->path, + O_RDONLY, (mode_t)data->set.new_file_perms); + if(!sshc->sftp_file) { + failf(data, "Could not open remote file for reading: %s", + ssh_get_error(sshc->ssh_session)); + + MOVE_TO_SFTP_CLOSE_STATE(); + break; + } + sftp_file_set_nonblocking(sshc->sftp_file); + state(data, SSH_SFTP_DOWNLOAD_STAT); + break; + + case SSH_SFTP_DOWNLOAD_STAT: + { + sftp_attributes attrs; + curl_off_t size; + + attrs = sftp_fstat(sshc->sftp_file); + if(!attrs || + !(attrs->flags & SSH_FILEXFER_ATTR_SIZE) || + (attrs->size == 0)) { + /* + * sftp_fstat didn't return an error, so maybe the server + * just doesn't support stat() + * OR the server doesn't return a file size with a stat() + * OR file size is 0 + */ + data->req.size = -1; + data->req.maxdownload = -1; + Curl_pgrsSetDownloadSize(data, -1); + size = 0; + } + else { + size = attrs->size; + + sftp_attributes_free(attrs); + + if(size < 0) { + failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(data->state.use_range) { + curl_off_t from, to; + char *ptr; + char *ptr2; + CURLofft to_t; + CURLofft from_t; + + from_t = curlx_strtoofft(data->state.range, &ptr, 10, &from); + if(from_t == CURL_OFFT_FLOW) { + return CURLE_RANGE_ERROR; + } + while(*ptr && (ISBLANK(*ptr) || (*ptr == '-'))) + ptr++; + to_t = curlx_strtoofft(ptr, &ptr2, 10, &to); + if(to_t == CURL_OFFT_FLOW) { + return CURLE_RANGE_ERROR; + } + if((to_t == CURL_OFFT_INVAL) /* no "to" value given */ + || (to >= size)) { + to = size - 1; + } + if(from_t) { + /* from is relative to end of file */ + from = size - to; + to = size - 1; + } + if(from > size) { + failf(data, "Offset (%" + CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" + CURL_FORMAT_CURL_OFF_T ")", from, size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(from > to) { + from = to; + size = 0; + } + else { + size = to - from + 1; + } + + rc = sftp_seek64(sshc->sftp_file, from); + if(rc) { + MOVE_TO_SFTP_CLOSE_STATE(); + break; + } + } + data->req.size = size; + data->req.maxdownload = size; + Curl_pgrsSetDownloadSize(data, size); + } + + /* We can resume if we can seek to the resume position */ + if(data->state.resume_from) { + if(data->state.resume_from < 0) { + /* We're supposed to download the last abs(from) bytes */ + if((curl_off_t)size < -data->state.resume_from) { + failf(data, "Offset (%" + CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" + CURL_FORMAT_CURL_OFF_T ")", + data->state.resume_from, size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* download from where? */ + data->state.resume_from += size; + } + else { + if((curl_off_t)size < data->state.resume_from) { + failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T + ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", + data->state.resume_from, size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + } + /* Now store the number of bytes we are expected to download */ + data->req.size = size - data->state.resume_from; + data->req.maxdownload = size - data->state.resume_from; + Curl_pgrsSetDownloadSize(data, + size - data->state.resume_from); + + rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); + if(rc) { + MOVE_TO_SFTP_CLOSE_STATE(); + break; + } + } + } + + /* Setup the actual download */ + if(data->req.size == 0) { + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + infof(data, "File already completely downloaded"); + state(data, SSH_STOP); + break; + } + Curl_setup_transfer(data, FIRSTSOCKET, data->req.size, FALSE, -1); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->writesockfd = conn->sockfd; + + /* we want to use the _receiving_ function even when the socket turns + out writableable as the underlying libssh recv function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_IN; + + if(result) { + /* this should never occur; the close state should be entered + at the time the error occurs */ + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = result; + } + else { + sshc->sftp_recv_state = 0; + state(data, SSH_STOP); + } + break; + + case SSH_SFTP_CLOSE: + if(sshc->sftp_file) { + sftp_close(sshc->sftp_file); + sshc->sftp_file = NULL; + } + Curl_safefree(protop->path); + + DEBUGF(infof(data, "SFTP DONE done")); + + /* Check if nextstate is set and move .nextstate could be POSTQUOTE_INIT + After nextstate is executed, the control should come back to + SSH_SFTP_CLOSE to pass the correct result back */ + if(sshc->nextstate != SSH_NO_STATE && + sshc->nextstate != SSH_SFTP_CLOSE) { + state(data, sshc->nextstate); + sshc->nextstate = SSH_SFTP_CLOSE; + } + else { + state(data, SSH_STOP); + result = sshc->actualcode; + } + break; + + case SSH_SFTP_SHUTDOWN: + /* during times we get here due to a broken transfer and then the + sftp_handle might not have been taken down so make sure that is done + before we proceed */ + + if(sshc->sftp_file) { + sftp_close(sshc->sftp_file); + sshc->sftp_file = NULL; + } + + if(sshc->sftp_session) { + sftp_free(sshc->sftp_session); + sshc->sftp_session = NULL; + } + + SSH_STRING_FREE_CHAR(sshc->homedir); + data->state.most_recent_ftp_entrypath = NULL; + + state(data, SSH_SESSION_DISCONNECT); + break; + + case SSH_SCP_TRANS_INIT: + result = Curl_getworkingpath(data, sshc->homedir, &protop->path); + if(result) { + sshc->actualcode = result; + state(data, SSH_STOP); + break; + } + + /* Functions from the SCP subsystem cannot handle/return SSH_AGAIN */ + ssh_set_blocking(sshc->ssh_session, 1); + + if(data->state.upload) { + if(data->state.infilesize < 0) { + failf(data, "SCP requires a known file size for upload"); + sshc->actualcode = CURLE_UPLOAD_FAILED; + MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED); + break; + } + + sshc->scp_session = + ssh_scp_new(sshc->ssh_session, SSH_SCP_WRITE, protop->path); + state(data, SSH_SCP_UPLOAD_INIT); + } + else { + sshc->scp_session = + ssh_scp_new(sshc->ssh_session, SSH_SCP_READ, protop->path); + state(data, SSH_SCP_DOWNLOAD_INIT); + } + + if(!sshc->scp_session) { + err_msg = ssh_get_error(sshc->ssh_session); + failf(data, "%s", err_msg); + MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED); + } + + break; + + case SSH_SCP_UPLOAD_INIT: + + rc = ssh_scp_init(sshc->scp_session); + if(rc != SSH_OK) { + err_msg = ssh_get_error(sshc->ssh_session); + failf(data, "%s", err_msg); + MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED); + break; + } + + rc = ssh_scp_push_file(sshc->scp_session, protop->path, + data->state.infilesize, + (int)data->set.new_file_perms); + if(rc != SSH_OK) { + err_msg = ssh_get_error(sshc->ssh_session); + failf(data, "%s", err_msg); + MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED); + break; + } + + /* upload data */ + Curl_setup_transfer(data, -1, data->req.size, FALSE, FIRSTSOCKET); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->sockfd = conn->writesockfd; + + /* store this original bitmask setup to use later on if we can't + figure out a "real" bitmask */ + sshc->orig_waitfor = data->req.keepon; + + /* we want to use the _sending_ function even when the socket turns + out readable as the underlying libssh scp send function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_OUT; + + state(data, SSH_STOP); + + break; + + case SSH_SCP_DOWNLOAD_INIT: + + rc = ssh_scp_init(sshc->scp_session); + if(rc != SSH_OK) { + err_msg = ssh_get_error(sshc->ssh_session); + failf(data, "%s", err_msg); + MOVE_TO_ERROR_STATE(CURLE_COULDNT_CONNECT); + break; + } + state(data, SSH_SCP_DOWNLOAD); + /* FALLTHROUGH */ + + case SSH_SCP_DOWNLOAD:{ + curl_off_t bytecount; + + rc = ssh_scp_pull_request(sshc->scp_session); + if(rc != SSH_SCP_REQUEST_NEWFILE) { + err_msg = ssh_get_error(sshc->ssh_session); + failf(data, "%s", err_msg); + MOVE_TO_ERROR_STATE(CURLE_REMOTE_FILE_NOT_FOUND); + break; + } + + /* download data */ + bytecount = ssh_scp_request_get_size(sshc->scp_session); + data->req.maxdownload = (curl_off_t) bytecount; + Curl_setup_transfer(data, FIRSTSOCKET, bytecount, FALSE, -1); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->writesockfd = conn->sockfd; + + /* we want to use the _receiving_ function even when the socket turns + out writableable as the underlying libssh recv function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_IN; + + state(data, SSH_STOP); + break; + } + case SSH_SCP_DONE: + if(data->state.upload) + state(data, SSH_SCP_SEND_EOF); + else + state(data, SSH_SCP_CHANNEL_FREE); + break; + + case SSH_SCP_SEND_EOF: + if(sshc->scp_session) { + rc = ssh_scp_close(sshc->scp_session); + if(rc == SSH_AGAIN) { + /* Currently the ssh_scp_close handles waiting for EOF in + * blocking way. + */ + break; + } + if(rc != SSH_OK) { + infof(data, "Failed to close libssh scp channel: %s", + ssh_get_error(sshc->ssh_session)); + } + } + + state(data, SSH_SCP_CHANNEL_FREE); + break; + + case SSH_SCP_CHANNEL_FREE: + if(sshc->scp_session) { + ssh_scp_free(sshc->scp_session); + sshc->scp_session = NULL; + } + DEBUGF(infof(data, "SCP DONE phase complete")); + + ssh_set_blocking(sshc->ssh_session, 0); + + state(data, SSH_SESSION_DISCONNECT); + /* FALLTHROUGH */ + + case SSH_SESSION_DISCONNECT: + /* during weird times when we've been prematurely aborted, the channel + is still alive when we reach this state and we MUST kill the channel + properly first */ + if(sshc->scp_session) { + ssh_scp_free(sshc->scp_session); + sshc->scp_session = NULL; + } + + ssh_disconnect(sshc->ssh_session); + if(!ssh_version(SSH_VERSION_INT(0, 10, 0))) { + /* conn->sock[FIRSTSOCKET] is closed by ssh_disconnect behind our back, + tell the connection to forget about it. This libssh + bug is fixed in 0.10.0. */ + Curl_conn_forget_socket(data, FIRSTSOCKET); + } + + SSH_STRING_FREE_CHAR(sshc->homedir); + data->state.most_recent_ftp_entrypath = NULL; + + state(data, SSH_SESSION_FREE); + /* FALLTHROUGH */ + case SSH_SESSION_FREE: + if(sshc->ssh_session) { + ssh_free(sshc->ssh_session); + sshc->ssh_session = NULL; + } + + /* worst-case scenario cleanup */ + + DEBUGASSERT(sshc->ssh_session == NULL); + DEBUGASSERT(sshc->scp_session == NULL); + + if(sshc->readdir_tmp) { + ssh_string_free_char(sshc->readdir_tmp); + sshc->readdir_tmp = NULL; + } + + if(sshc->quote_attrs) + sftp_attributes_free(sshc->quote_attrs); + + if(sshc->readdir_attrs) + sftp_attributes_free(sshc->readdir_attrs); + + if(sshc->readdir_link_attrs) + sftp_attributes_free(sshc->readdir_link_attrs); + + if(sshc->privkey) + ssh_key_free(sshc->privkey); + if(sshc->pubkey) + ssh_key_free(sshc->pubkey); + + Curl_safefree(sshc->rsa_pub); + Curl_safefree(sshc->rsa); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + Curl_dyn_free(&sshc->readdir_buf); + Curl_safefree(sshc->readdir_linkPath); + SSH_STRING_FREE_CHAR(sshc->homedir); + + /* the code we are about to return */ + result = sshc->actualcode; + + memset(sshc, 0, sizeof(struct ssh_conn)); + + connclose(conn, "SSH session free"); + sshc->state = SSH_SESSION_FREE; /* current */ + sshc->nextstate = SSH_NO_STATE; + state(data, SSH_STOP); + break; + + case SSH_QUIT: + /* fallthrough, just stop! */ + default: + /* internal error */ + sshc->nextstate = SSH_NO_STATE; + state(data, SSH_STOP); + break; + + } + } while(!rc && (sshc->state != SSH_STOP)); + + + if(rc == SSH_AGAIN) { + /* we would block, we need to wait for the socket to be ready (in the + right direction too)! */ + *block = TRUE; + } + + 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 int myssh_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *sock) +{ + int bitmap = GETSOCK_BLANK; + (void)data; + sock[0] = conn->sock[FIRSTSOCKET]; + + if(conn->waitfor & KEEP_RECV) + bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); + + if(conn->waitfor & KEEP_SEND) + bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); + + if(!conn->waitfor) + bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); + + return bitmap; +} + +static void myssh_block2waitfor(struct connectdata *conn, bool block) +{ + struct ssh_conn *sshc = &conn->proto.sshc; + + /* If it didn't block, or nothing was returned by ssh_get_poll_flags + * have the original set */ + conn->waitfor = sshc->orig_waitfor; + + if(block) { + int dir = ssh_get_poll_flags(sshc->ssh_session); + if(dir & SSH_READ_PENDING) { + /* translate the libssh define bits into our own bit defines */ + conn->waitfor = KEEP_RECV; + } + else if(dir & SSH_WRITE_PENDING) { + conn->waitfor = KEEP_SEND; + } + } +} + +/* called repeatedly until done from multi.c */ +static CURLcode myssh_multi_statemach(struct Curl_easy *data, + bool *done) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + bool block; /* we store the status and use that to provide a ssh_getsock() + implementation */ + CURLcode result = myssh_statemach_act(data, &block); + + *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; + myssh_block2waitfor(conn, block); + + return result; +} + +static CURLcode myssh_block_statemach(struct Curl_easy *data, + bool disconnect) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + CURLcode result = CURLE_OK; + + while((sshc->state != SSH_STOP) && !result) { + bool block; + timediff_t left = 1000; + struct curltime now = Curl_now(); + + result = myssh_statemach_act(data, &block); + if(result) + break; + + if(!disconnect) { + if(Curl_pgrsUpdate(data)) + return CURLE_ABORTED_BY_CALLBACK; + + result = Curl_speedcheck(data, now); + if(result) + break; + + left = Curl_timeleft(data, NULL, FALSE); + if(left < 0) { + failf(data, "Operation timed out"); + return CURLE_OPERATION_TIMEDOUT; + } + } + + if(block) { + curl_socket_t fd_read = conn->sock[FIRSTSOCKET]; + /* wait for the socket to become ready */ + (void) Curl_socket_check(fd_read, CURL_SOCKET_BAD, + CURL_SOCKET_BAD, left > 1000 ? 1000 : left); + } + + } + + return result; +} + +/* + * SSH setup connection + */ +static CURLcode myssh_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + struct SSHPROTO *ssh; + struct ssh_conn *sshc = &conn->proto.sshc; + + data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); + if(!ssh) + return CURLE_OUT_OF_MEMORY; + Curl_dyn_init(&sshc->readdir_buf, PATH_MAX * 2); + + return CURLE_OK; +} + +static Curl_recv scp_recv, sftp_recv; +static Curl_send scp_send, sftp_send; + +/* + * Curl_ssh_connect() gets called from Curl_protocol_connect() to allow us to + * do protocol-specific actions at connect-time. + */ +static CURLcode myssh_connect(struct Curl_easy *data, bool *done) +{ + struct ssh_conn *ssh; + CURLcode result; + struct connectdata *conn = data->conn; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int rc; + + /* initialize per-handle data if not already */ + if(!data->req.p.ssh) + myssh_setup_connection(data, conn); + + /* We default to persistent connections. We set this already in this connect + function to make the reuse checks properly be able to check this bit. */ + connkeep(conn, "SSH default"); + + if(conn->handler->protocol & CURLPROTO_SCP) { + conn->recv[FIRSTSOCKET] = scp_recv; + conn->send[FIRSTSOCKET] = scp_send; + } + else { + conn->recv[FIRSTSOCKET] = sftp_recv; + conn->send[FIRSTSOCKET] = sftp_send; + } + + ssh = &conn->proto.sshc; + + ssh->ssh_session = ssh_new(); + if(!ssh->ssh_session) { + failf(data, "Failure initialising ssh session"); + return CURLE_FAILED_INIT; + } + + rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_HOST, conn->host.name); + if(rc != SSH_OK) { + failf(data, "Could not set remote host"); + return CURLE_FAILED_INIT; + } + + rc = ssh_options_parse_config(ssh->ssh_session, NULL); + if(rc != SSH_OK) { + infof(data, "Could not parse SSH configuration files"); + /* ignore */ + } + + rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_FD, &sock); + if(rc != SSH_OK) { + failf(data, "Could not set socket"); + return CURLE_FAILED_INIT; + } + + if(conn->user && conn->user[0] != '\0') { + infof(data, "User: %s", conn->user); + rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_USER, conn->user); + if(rc != SSH_OK) { + failf(data, "Could not set user"); + return CURLE_FAILED_INIT; + } + } + + if(data->set.str[STRING_SSH_KNOWNHOSTS]) { + infof(data, "Known hosts: %s", data->set.str[STRING_SSH_KNOWNHOSTS]); + rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_KNOWNHOSTS, + data->set.str[STRING_SSH_KNOWNHOSTS]); + if(rc != SSH_OK) { + failf(data, "Could not set known hosts file path"); + return CURLE_FAILED_INIT; + } + } + + if(conn->remote_port) { + rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_PORT, + &conn->remote_port); + if(rc != SSH_OK) { + failf(data, "Could not set remote port"); + return CURLE_FAILED_INIT; + } + } + + if(data->set.ssh_compression) { + rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_COMPRESSION, + "zlib,zlib@openssh.com,none"); + if(rc != SSH_OK) { + failf(data, "Could not set compression"); + return CURLE_FAILED_INIT; + } + } + + ssh->privkey = NULL; + ssh->pubkey = NULL; + + if(data->set.str[STRING_SSH_PUBLIC_KEY]) { + rc = ssh_pki_import_pubkey_file(data->set.str[STRING_SSH_PUBLIC_KEY], + &ssh->pubkey); + if(rc != SSH_OK) { + failf(data, "Could not load public key file"); + return CURLE_FAILED_INIT; + } + } + + /* we do not verify here, we do it at the state machine, + * after connection */ + + state(data, SSH_INIT); + + result = myssh_multi_statemach(data, done); + + return result; +} + +/* called from multi.c while DOing */ +static CURLcode scp_doing(struct Curl_easy *data, bool *dophase_done) +{ + CURLcode result; + + result = myssh_multi_statemach(data, dophase_done); + + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete")); + } + return result; +} + +/* + *********************************************************************** + * + * scp_perform() + * + * This is the actual DO function for SCP. Get a file according to + * the options previously setup. + */ + +static +CURLcode scp_perform(struct Curl_easy *data, + bool *connected, bool *dophase_done) +{ + CURLcode result = CURLE_OK; + + DEBUGF(infof(data, "DO phase starts")); + + *dophase_done = FALSE; /* not done yet */ + + /* start the first command in the DO phase */ + state(data, SSH_SCP_TRANS_INIT); + + result = myssh_multi_statemach(data, dophase_done); + + *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); + + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete")); + } + + return result; +} + +static CURLcode myssh_do_it(struct Curl_easy *data, bool *done) +{ + CURLcode result; + bool connected = 0; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + + *done = FALSE; /* default to false */ + + data->req.size = -1; /* make sure this is unknown at this point */ + + sshc->actualcode = CURLE_OK; /* reset error code */ + sshc->secondCreateDirs = 0; /* reset the create dir attempt state + variable */ + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, -1); + Curl_pgrsSetDownloadSize(data, -1); + + if(conn->handler->protocol & CURLPROTO_SCP) + result = scp_perform(data, &connected, done); + else + result = sftp_perform(data, &connected, done); + + return result; +} + +/* BLOCKING, but the function is using the state machine so the only reason + this is still blocking is that the multi interface code has no support for + disconnecting operations that takes a while */ +static CURLcode scp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + CURLcode result = CURLE_OK; + struct ssh_conn *ssh = &conn->proto.sshc; + (void) dead_connection; + + if(ssh->ssh_session) { + /* only if there's a session still around to use! */ + + state(data, SSH_SESSION_DISCONNECT); + + result = myssh_block_statemach(data, TRUE); + } + + return result; +} + +/* generic done function for both SCP and SFTP called from their specific + done functions */ +static CURLcode myssh_done(struct Curl_easy *data, CURLcode status) +{ + CURLcode result = CURLE_OK; + struct SSHPROTO *protop = data->req.p.ssh; + + if(!status) { + /* run the state-machine */ + result = myssh_block_statemach(data, FALSE); + } + else + result = status; + + if(protop) + Curl_safefree(protop->path); + if(Curl_pgrsDone(data)) + return CURLE_ABORTED_BY_CALLBACK; + + data->req.keepon = 0; /* clear all bits */ + return result; +} + + +static CURLcode scp_done(struct Curl_easy *data, CURLcode status, + bool premature) +{ + (void) premature; /* not used */ + + if(!status) + state(data, SSH_SCP_DONE); + + return myssh_done(data, status); + +} + +static ssize_t scp_send(struct Curl_easy *data, int sockindex, + const void *mem, size_t len, CURLcode *err) +{ + int rc; + struct connectdata *conn = data->conn; + (void) sockindex; /* we only support SCP on the fixed known primary socket */ + (void) err; + + rc = ssh_scp_write(conn->proto.sshc.scp_session, mem, len); + +#if 0 + /* The following code is misleading, mostly added as wishful thinking + * that libssh at some point will implement non-blocking ssh_scp_write/read. + * Currently rc can only be number of bytes read or SSH_ERROR. */ + myssh_block2waitfor(conn, (rc == SSH_AGAIN) ? TRUE : FALSE); + + if(rc == SSH_AGAIN) { + *err = CURLE_AGAIN; + return 0; + } + else +#endif + if(rc != SSH_OK) { + *err = CURLE_SSH; + return -1; + } + + return len; +} + +static ssize_t scp_recv(struct Curl_easy *data, int sockindex, + char *mem, size_t len, CURLcode *err) +{ + ssize_t nread; + struct connectdata *conn = data->conn; + (void) err; + (void) sockindex; /* we only support SCP on the fixed known primary socket */ + + /* libssh returns int */ + nread = ssh_scp_read(conn->proto.sshc.scp_session, mem, len); + +#if 0 + /* The following code is misleading, mostly added as wishful thinking + * that libssh at some point will implement non-blocking ssh_scp_write/read. + * Currently rc can only be SSH_OK or SSH_ERROR. */ + + myssh_block2waitfor(conn, (nread == SSH_AGAIN) ? TRUE : FALSE); + if(nread == SSH_AGAIN) { + *err = CURLE_AGAIN; + nread = -1; + } +#endif + + return nread; +} + +/* + * =============== SFTP =============== + */ + +/* + *********************************************************************** + * + * sftp_perform() + * + * This is the actual DO function for SFTP. Get a file/directory according to + * the options previously setup. + */ + +static +CURLcode sftp_perform(struct Curl_easy *data, + bool *connected, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + + DEBUGF(infof(data, "DO phase starts")); + + *dophase_done = FALSE; /* not done yet */ + + /* start the first command in the DO phase */ + state(data, SSH_SFTP_QUOTE_INIT); + + /* run the state-machine */ + result = myssh_multi_statemach(data, dophase_done); + + *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); + + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete")); + } + + return result; +} + +/* called from multi.c while DOing */ +static CURLcode sftp_doing(struct Curl_easy *data, + bool *dophase_done) +{ + CURLcode result = myssh_multi_statemach(data, dophase_done); + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete")); + } + return result; +} + +/* BLOCKING, but the function is using the state machine so the only reason + this is still blocking is that the multi interface code has no support for + disconnecting operations that takes a while */ +static CURLcode sftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + CURLcode result = CURLE_OK; + (void) dead_connection; + + DEBUGF(infof(data, "SSH DISCONNECT starts now")); + + if(conn->proto.sshc.ssh_session) { + /* only if there's a session still around to use! */ + state(data, SSH_SFTP_SHUTDOWN); + result = myssh_block_statemach(data, TRUE); + } + + DEBUGF(infof(data, "SSH DISCONNECT is done")); + + return result; + +} + +static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, + bool premature) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + + if(!status) { + /* Post quote commands are executed after the SFTP_CLOSE state to avoid + errors that could happen due to open file handles during POSTQUOTE + operation */ + if(!premature && data->set.postquote && !conn->bits.retry) + sshc->nextstate = SSH_SFTP_POSTQUOTE_INIT; + state(data, SSH_SFTP_CLOSE); + } + return myssh_done(data, status); +} + +/* return number of sent bytes */ +static ssize_t sftp_send(struct Curl_easy *data, int sockindex, + const void *mem, size_t len, CURLcode *err) +{ + ssize_t nwrite; + struct connectdata *conn = data->conn; + (void)sockindex; + + /* limit the writes to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + */ + if(len > 32768) + len = 32768; + + nwrite = sftp_write(conn->proto.sshc.sftp_file, mem, len); + + myssh_block2waitfor(conn, FALSE); + +#if 0 /* not returned by libssh on write */ + if(nwrite == SSH_AGAIN) { + *err = CURLE_AGAIN; + nwrite = 0; + } + else +#endif + if(nwrite < 0) { + *err = CURLE_SSH; + nwrite = -1; + } + + return nwrite; +} + +/* + * Return number of received (decrypted) bytes + * or <0 on error + */ +static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, + char *mem, size_t len, CURLcode *err) +{ + ssize_t nread; + struct connectdata *conn = data->conn; + (void)sockindex; + + DEBUGASSERT(len < CURL_MAX_READ_SIZE); + + switch(conn->proto.sshc.sftp_recv_state) { + case 0: + conn->proto.sshc.sftp_file_index = + sftp_async_read_begin(conn->proto.sshc.sftp_file, + (uint32_t)len); + if(conn->proto.sshc.sftp_file_index < 0) { + *err = CURLE_RECV_ERROR; + return -1; + } + + /* FALLTHROUGH */ + case 1: + conn->proto.sshc.sftp_recv_state = 1; + + nread = sftp_async_read(conn->proto.sshc.sftp_file, + mem, (uint32_t)len, + conn->proto.sshc.sftp_file_index); + + myssh_block2waitfor(conn, (nread == SSH_AGAIN)?TRUE:FALSE); + + if(nread == SSH_AGAIN) { + *err = CURLE_AGAIN; + return -1; + } + else if(nread < 0) { + *err = CURLE_RECV_ERROR; + return -1; + } + + conn->proto.sshc.sftp_recv_state = 0; + return nread; + + default: + /* we never reach here */ + return -1; + } +} + +static void sftp_quote(struct Curl_easy *data) +{ + const char *cp; + struct connectdata *conn = data->conn; + struct SSHPROTO *protop = data->req.p.ssh; + struct ssh_conn *sshc = &conn->proto.sshc; + CURLcode result; + + /* + * Support some of the "FTP" commands + */ + char *cmd = sshc->quote_item->data; + sshc->acceptfail = FALSE; + + /* if a command starts with an asterisk, which a legal SFTP command never + can, the command will be allowed to fail without it causing any + aborts or cancels etc. It will cause libcurl to act as if the command + is successful, whatever the server responds. */ + + if(cmd[0] == '*') { + cmd++; + sshc->acceptfail = TRUE; + } + + if(strcasecompare("pwd", cmd)) { + /* output debug output if that is requested */ + char *tmp = aprintf("257 \"%s\" is current directory.\n", + protop->path); + if(!tmp) { + sshc->actualcode = CURLE_OUT_OF_MEMORY; + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return; + } + Curl_debug(data, CURLINFO_HEADER_OUT, (char *) "PWD\n", 4); + Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp)); + + /* this sends an FTP-like "header" to the header callback so that the + current directory can be read very similar to how it is read when + using ordinary FTP. */ + result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); + free(tmp); + if(result) { + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + } + else + state(data, SSH_SFTP_NEXT_QUOTE); + return; + } + + /* + * the arguments following the command must be separated from the + * command with a space so we can check for it unconditionally + */ + cp = strchr(cmd, ' '); + if(!cp) { + failf(data, "Syntax error in SFTP command. Supply parameter(s)"); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + return; + } + + /* + * also, every command takes at least one argument so we get that + * first argument right now + */ + result = Curl_get_pathname(&cp, &sshc->quote_path1, sshc->homedir); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error: Bad first parameter"); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + return; + } + + /* + * SFTP is a binary protocol, so we don't send text commands + * to the server. Instead, we scan for commands used by + * OpenSSH's sftp program and call the appropriate libssh + * functions. + */ + if(strncasecompare(cmd, "chgrp ", 6) || + strncasecompare(cmd, "chmod ", 6) || + strncasecompare(cmd, "chown ", 6) || + strncasecompare(cmd, "atime ", 6) || + strncasecompare(cmd, "mtime ", 6)) { + /* attribute change */ + + /* sshc->quote_path1 contains the mode to set */ + /* get the destination */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error in chgrp/chmod/chown/atime/mtime: " + "Bad second parameter"); + Curl_safefree(sshc->quote_path1); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + return; + } + sshc->quote_attrs = NULL; + state(data, SSH_SFTP_QUOTE_STAT); + return; + } + if(strncasecompare(cmd, "ln ", 3) || + strncasecompare(cmd, "symlink ", 8)) { + /* symbolic linking */ + /* sshc->quote_path1 is the source */ + /* get the destination */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error in ln/symlink: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + return; + } + state(data, SSH_SFTP_QUOTE_SYMLINK); + return; + } + else if(strncasecompare(cmd, "mkdir ", 6)) { + /* create dir */ + state(data, SSH_SFTP_QUOTE_MKDIR); + return; + } + else if(strncasecompare(cmd, "rename ", 7)) { + /* rename file */ + /* first param is the source path */ + /* second param is the dest. path */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error in rename: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + return; + } + state(data, SSH_SFTP_QUOTE_RENAME); + return; + } + else if(strncasecompare(cmd, "rmdir ", 6)) { + /* delete dir */ + state(data, SSH_SFTP_QUOTE_RMDIR); + return; + } + else if(strncasecompare(cmd, "rm ", 3)) { + state(data, SSH_SFTP_QUOTE_UNLINK); + return; + } +#ifdef HAS_STATVFS_SUPPORT + else if(strncasecompare(cmd, "statvfs ", 8)) { + state(data, SSH_SFTP_QUOTE_STATVFS); + return; + } +#endif + + failf(data, "Unknown SFTP command"); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; +} + +static void sftp_quote_stat(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + char *cmd = sshc->quote_item->data; + sshc->acceptfail = FALSE; + + /* if a command starts with an asterisk, which a legal SFTP command never + can, the command will be allowed to fail without it causing any + aborts or cancels etc. It will cause libcurl to act as if the command + is successful, whatever the server responds. */ + + if(cmd[0] == '*') { + cmd++; + sshc->acceptfail = TRUE; + } + + /* We read the file attributes, store them in sshc->quote_attrs + * and modify them accordingly to command. Then we switch to + * QUOTE_SETSTAT state to write new ones. + */ + + if(sshc->quote_attrs) + sftp_attributes_free(sshc->quote_attrs); + sshc->quote_attrs = sftp_stat(sshc->sftp_session, sshc->quote_path2); + if(!sshc->quote_attrs) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Attempt to get SFTP stats failed: %d", + sftp_get_error(sshc->sftp_session)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + return; + } + + /* Now set the new attributes... */ + if(strncasecompare(cmd, "chgrp", 5)) { + sshc->quote_attrs->gid = (uint32_t)strtoul(sshc->quote_path1, NULL, 10); + if(sshc->quote_attrs->gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && + !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Syntax error: chgrp gid not a number"); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + return; + } + sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_UIDGID; + } + else if(strncasecompare(cmd, "chmod", 5)) { + mode_t perms; + perms = (mode_t)strtoul(sshc->quote_path1, NULL, 8); + /* permissions are octal */ + if(perms == 0 && !ISDIGIT(sshc->quote_path1[0])) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Syntax error: chmod permissions not a number"); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + return; + } + sshc->quote_attrs->permissions = perms; + sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_PERMISSIONS; + } + else if(strncasecompare(cmd, "chown", 5)) { + sshc->quote_attrs->uid = (uint32_t)strtoul(sshc->quote_path1, NULL, 10); + if(sshc->quote_attrs->uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && + !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Syntax error: chown uid not a number"); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + return; + } + sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_UIDGID; + } + else if(strncasecompare(cmd, "atime", 5) || + strncasecompare(cmd, "mtime", 5)) { + time_t date = Curl_getdate_capped(sshc->quote_path1); + bool fail = FALSE; + if(date == -1) { + failf(data, "incorrect date format for %.*s", 5, cmd); + fail = TRUE; + } +#if SIZEOF_TIME_T > 4 + else if(date > 0xffffffff) { + failf(data, "date overflow"); + fail = TRUE; /* avoid setting a capped time */ + } +#endif + if(fail) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + return; + } + if(strncasecompare(cmd, "atime", 5)) + sshc->quote_attrs->atime = (uint32_t)date; + else /* mtime */ + sshc->quote_attrs->mtime = (uint32_t)date; + + sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_ACMODTIME; + } + + /* Now send the completed structure... */ + state(data, SSH_SFTP_QUOTE_SETSTAT); + return; +} + +CURLcode Curl_ssh_init(void) +{ + if(ssh_init()) { + DEBUGF(fprintf(stderr, "Error: libssh_init failed\n")); + return CURLE_FAILED_INIT; + } + return CURLE_OK; +} + +void Curl_ssh_cleanup(void) +{ + (void)ssh_finalize(); +} + +void Curl_ssh_version(char *buffer, size_t buflen) +{ + (void)msnprintf(buffer, buflen, "libssh/%s", ssh_version(0)); +} + +#if defined(__GNUC__) && \ + (LIBSSH_VERSION_MINOR >= 10) || \ + (LIBSSH_VERSION_MAJOR > 0) +#pragma GCC diagnostic pop +#endif + +#endif /* USE_LIBSSH */ diff --git a/Utilities/cmcurl/lib/vssh/libssh2.c b/Utilities/cmcurl/lib/vssh/libssh2.c new file mode 100644 index 0000000..11f5f4f --- /dev/null +++ b/Utilities/cmcurl/lib/vssh/libssh2.c @@ -0,0 +1,3822 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* #define CURL_LIBSSH2_DEBUG */ + +#include "curl_setup.h" + +#ifdef USE_LIBSSH2 + +#include <limits.h> + +#include <libssh2.h> +#include <libssh2_sftp.h> + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include <curl/curl.h> +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "progress.h" +#include "transfer.h" +#include "escape.h" +#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "ssh.h" +#include "url.h" +#include "speedcheck.h" +#include "getinfo.h" +#include "strdup.h" +#include "strcase.h" +#include "vtls/vtls.h" +#include "cfilters.h" +#include "connect.h" +#include "inet_ntop.h" +#include "parsedate.h" /* for the week day and month names */ +#include "sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "strtoofft.h" +#include "multiif.h" +#include "select.h" +#include "warnless.h" +#include "curl_path.h" + +#include <curl_base64.h> /* for base64 encoding/decoding */ +#include <curl_sha256.h> + + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#if LIBSSH2_VERSION_NUM >= 0x010206 +/* libssh2_sftp_statvfs and friends were added in 1.2.6 */ +#define HAS_STATVFS_SUPPORT 1 +#endif + +#define sftp_libssh2_realpath(s,p,t,m) \ + libssh2_sftp_symlink_ex((s), (p), curlx_uztoui(strlen(p)), \ + (t), (m), LIBSSH2_SFTP_REALPATH) + +/* Local functions: */ +static const char *sftp_libssh2_strerror(unsigned long err); +static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc); +static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc); +static LIBSSH2_FREE_FUNC(my_libssh2_free); +static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data); +static CURLcode ssh_connect(struct Curl_easy *data, bool *done); +static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done); +static CURLcode ssh_do(struct Curl_easy *data, bool *done); +static CURLcode scp_done(struct Curl_easy *data, CURLcode c, bool premature); +static CURLcode scp_doing(struct Curl_easy *data, bool *dophase_done); +static CURLcode scp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection); +static CURLcode sftp_done(struct Curl_easy *data, CURLcode, bool premature); +static CURLcode sftp_doing(struct Curl_easy *data, bool *dophase_done); +static CURLcode sftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead); +static CURLcode sftp_perform(struct Curl_easy *data, bool *connected, + bool *dophase_done); +static int ssh_getsock(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *sock); +static CURLcode ssh_setup_connection(struct Curl_easy *data, + struct connectdata *conn); +static void ssh_attach(struct Curl_easy *data, struct connectdata *conn); + +/* + * SCP protocol handler. + */ + +const struct Curl_handler Curl_handler_scp = { + "SCP", /* scheme */ + ssh_setup_connection, /* setup_connection */ + ssh_do, /* do_it */ + scp_done, /* done */ + ZERO_NULL, /* do_more */ + ssh_connect, /* connect_it */ + ssh_multi_statemach, /* connecting */ + scp_doing, /* doing */ + ssh_getsock, /* proto_getsock */ + ssh_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ssh_getsock, /* perform_getsock */ + scp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ssh_attach, /* attach */ + PORT_SSH, /* defport */ + CURLPROTO_SCP, /* protocol */ + CURLPROTO_SCP, /* family */ + PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION + | PROTOPT_NOURLQUERY /* flags */ +}; + + +/* + * SFTP protocol handler. + */ + +const struct Curl_handler Curl_handler_sftp = { + "SFTP", /* scheme */ + ssh_setup_connection, /* setup_connection */ + ssh_do, /* do_it */ + sftp_done, /* done */ + ZERO_NULL, /* do_more */ + ssh_connect, /* connect_it */ + ssh_multi_statemach, /* connecting */ + sftp_doing, /* doing */ + ssh_getsock, /* proto_getsock */ + ssh_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ssh_getsock, /* perform_getsock */ + sftp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ssh_attach, /* attach */ + PORT_SSH, /* defport */ + CURLPROTO_SFTP, /* protocol */ + CURLPROTO_SFTP, /* family */ + PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION + | PROTOPT_NOURLQUERY /* flags */ +}; + +static void +kbd_callback(const char *name, int name_len, const char *instruction, + int instruction_len, int num_prompts, + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, + void **abstract) +{ + struct Curl_easy *data = (struct Curl_easy *)*abstract; + +#ifdef CURL_LIBSSH2_DEBUG + fprintf(stderr, "name=%s\n", name); + fprintf(stderr, "name_len=%d\n", name_len); + fprintf(stderr, "instruction=%s\n", instruction); + fprintf(stderr, "instruction_len=%d\n", instruction_len); + fprintf(stderr, "num_prompts=%d\n", num_prompts); +#else + (void)name; + (void)name_len; + (void)instruction; + (void)instruction_len; +#endif /* CURL_LIBSSH2_DEBUG */ + if(num_prompts == 1) { + struct connectdata *conn = data->conn; + responses[0].text = strdup(conn->passwd); + responses[0].length = curlx_uztoui(strlen(conn->passwd)); + } + (void)prompts; +} /* kbd_callback */ + +static CURLcode sftp_libssh2_error_to_CURLE(unsigned long err) +{ + switch(err) { + case LIBSSH2_FX_OK: + return CURLE_OK; + + case LIBSSH2_FX_NO_SUCH_FILE: + case LIBSSH2_FX_NO_SUCH_PATH: + return CURLE_REMOTE_FILE_NOT_FOUND; + + case LIBSSH2_FX_PERMISSION_DENIED: + case LIBSSH2_FX_WRITE_PROTECT: + case LIBSSH2_FX_LOCK_CONFlICT: + return CURLE_REMOTE_ACCESS_DENIED; + + case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: + case LIBSSH2_FX_QUOTA_EXCEEDED: + return CURLE_REMOTE_DISK_FULL; + + case LIBSSH2_FX_FILE_ALREADY_EXISTS: + return CURLE_REMOTE_FILE_EXISTS; + + case LIBSSH2_FX_DIR_NOT_EMPTY: + return CURLE_QUOTE_ERROR; + + default: + break; + } + + return CURLE_SSH; +} + +static CURLcode libssh2_session_error_to_CURLE(int err) +{ + switch(err) { + /* Ordered by order of appearance in libssh2.h */ + case LIBSSH2_ERROR_NONE: + return CURLE_OK; + + /* This is the error returned by libssh2_scp_recv2 + * on unknown file */ + case LIBSSH2_ERROR_SCP_PROTOCOL: + return CURLE_REMOTE_FILE_NOT_FOUND; + + case LIBSSH2_ERROR_SOCKET_NONE: + return CURLE_COULDNT_CONNECT; + + case LIBSSH2_ERROR_ALLOC: + return CURLE_OUT_OF_MEMORY; + + case LIBSSH2_ERROR_SOCKET_SEND: + return CURLE_SEND_ERROR; + + case LIBSSH2_ERROR_HOSTKEY_INIT: + case LIBSSH2_ERROR_HOSTKEY_SIGN: + case LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED: + case LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED: + return CURLE_PEER_FAILED_VERIFICATION; + + case LIBSSH2_ERROR_PASSWORD_EXPIRED: + return CURLE_LOGIN_DENIED; + + case LIBSSH2_ERROR_SOCKET_TIMEOUT: + case LIBSSH2_ERROR_TIMEOUT: + return CURLE_OPERATION_TIMEDOUT; + + case LIBSSH2_ERROR_EAGAIN: + return CURLE_AGAIN; + } + + return CURLE_SSH; +} + +static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc) +{ + (void)abstract; /* arg not used */ + return malloc(count); +} + +static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc) +{ + (void)abstract; /* arg not used */ + return realloc(ptr, count); +} + +static LIBSSH2_FREE_FUNC(my_libssh2_free) +{ + (void)abstract; /* arg not used */ + if(ptr) /* ssh2 agent sometimes call free with null ptr */ + free(ptr); +} + +/* + * SSH State machine related code + */ +/* This is the ONLY way to change SSH state! */ +static void state(struct Curl_easy *data, sshstate nowstate) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[] = { + "SSH_STOP", + "SSH_INIT", + "SSH_S_STARTUP", + "SSH_HOSTKEY", + "SSH_AUTHLIST", + "SSH_AUTH_PKEY_INIT", + "SSH_AUTH_PKEY", + "SSH_AUTH_PASS_INIT", + "SSH_AUTH_PASS", + "SSH_AUTH_AGENT_INIT", + "SSH_AUTH_AGENT_LIST", + "SSH_AUTH_AGENT", + "SSH_AUTH_HOST_INIT", + "SSH_AUTH_HOST", + "SSH_AUTH_KEY_INIT", + "SSH_AUTH_KEY", + "SSH_AUTH_GSSAPI", + "SSH_AUTH_DONE", + "SSH_SFTP_INIT", + "SSH_SFTP_REALPATH", + "SSH_SFTP_QUOTE_INIT", + "SSH_SFTP_POSTQUOTE_INIT", + "SSH_SFTP_QUOTE", + "SSH_SFTP_NEXT_QUOTE", + "SSH_SFTP_QUOTE_STAT", + "SSH_SFTP_QUOTE_SETSTAT", + "SSH_SFTP_QUOTE_SYMLINK", + "SSH_SFTP_QUOTE_MKDIR", + "SSH_SFTP_QUOTE_RENAME", + "SSH_SFTP_QUOTE_RMDIR", + "SSH_SFTP_QUOTE_UNLINK", + "SSH_SFTP_QUOTE_STATVFS", + "SSH_SFTP_GETINFO", + "SSH_SFTP_FILETIME", + "SSH_SFTP_TRANS_INIT", + "SSH_SFTP_UPLOAD_INIT", + "SSH_SFTP_CREATE_DIRS_INIT", + "SSH_SFTP_CREATE_DIRS", + "SSH_SFTP_CREATE_DIRS_MKDIR", + "SSH_SFTP_READDIR_INIT", + "SSH_SFTP_READDIR", + "SSH_SFTP_READDIR_LINK", + "SSH_SFTP_READDIR_BOTTOM", + "SSH_SFTP_READDIR_DONE", + "SSH_SFTP_DOWNLOAD_INIT", + "SSH_SFTP_DOWNLOAD_STAT", + "SSH_SFTP_CLOSE", + "SSH_SFTP_SHUTDOWN", + "SSH_SCP_TRANS_INIT", + "SSH_SCP_UPLOAD_INIT", + "SSH_SCP_DOWNLOAD_INIT", + "SSH_SCP_DOWNLOAD", + "SSH_SCP_DONE", + "SSH_SCP_SEND_EOF", + "SSH_SCP_WAIT_EOF", + "SSH_SCP_WAIT_CLOSE", + "SSH_SCP_CHANNEL_FREE", + "SSH_SESSION_DISCONNECT", + "SSH_SESSION_FREE", + "QUIT" + }; + + /* a precaution to make sure the lists are in sync */ + DEBUGASSERT(sizeof(names)/sizeof(names[0]) == SSH_LAST); + + if(sshc->state != nowstate) { + infof(data, "SFTP %p state change from %s to %s", + (void *)sshc, names[sshc->state], names[nowstate]); + } +#endif + + sshc->state = nowstate; +} + + +#ifdef HAVE_LIBSSH2_KNOWNHOST_API +static int sshkeycallback(struct Curl_easy *easy, + const struct curl_khkey *knownkey, /* known */ + const struct curl_khkey *foundkey, /* found */ + enum curl_khmatch match, + void *clientp) +{ + (void)easy; + (void)knownkey; + (void)foundkey; + (void)clientp; + + /* we only allow perfect matches, and we reject everything else */ + return (match != CURLKHMATCH_OK)?CURLKHSTAT_REJECT:CURLKHSTAT_FINE; +} +#endif + +/* + * Earlier libssh2 versions didn't have the ability to seek to 64bit positions + * with 32bit size_t. + */ +#ifdef HAVE_LIBSSH2_SFTP_SEEK64 +#define SFTP_SEEK(x,y) libssh2_sftp_seek64(x, (libssh2_uint64_t)y) +#else +#define SFTP_SEEK(x,y) libssh2_sftp_seek(x, (size_t)y) +#endif + +/* + * Earlier libssh2 versions didn't do SCP properly beyond 32bit sizes on 32bit + * architectures so we check of the necessary function is present. + */ +#ifndef HAVE_LIBSSH2_SCP_SEND64 +#define SCP_SEND(a,b,c,d) libssh2_scp_send_ex(a, b, (int)(c), (size_t)d, 0, 0) +#else +#define SCP_SEND(a,b,c,d) libssh2_scp_send64(a, b, (int)(c), \ + (libssh2_uint64_t)d, 0, 0) +#endif + +/* + * libssh2 1.2.8 fixed the problem with 32bit ints used for sockets on win64. + */ +#ifdef HAVE_LIBSSH2_SESSION_HANDSHAKE +#define session_startup(x,y) libssh2_session_handshake(x, y) +#else +#define session_startup(x,y) libssh2_session_startup(x, (int)y) +#endif +static int convert_ssh2_keytype(int sshkeytype) +{ + int keytype = CURLKHTYPE_UNKNOWN; + switch(sshkeytype) { + case LIBSSH2_HOSTKEY_TYPE_RSA: + keytype = CURLKHTYPE_RSA; + break; + case LIBSSH2_HOSTKEY_TYPE_DSS: + keytype = CURLKHTYPE_DSS; + break; +#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 + case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: + keytype = CURLKHTYPE_ECDSA; + break; +#endif +#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_384 + case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: + keytype = CURLKHTYPE_ECDSA; + break; +#endif +#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_521 + case LIBSSH2_HOSTKEY_TYPE_ECDSA_521: + keytype = CURLKHTYPE_ECDSA; + break; +#endif +#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 + case LIBSSH2_HOSTKEY_TYPE_ED25519: + keytype = CURLKHTYPE_ED25519; + break; +#endif + } + return keytype; +} + +static CURLcode ssh_knownhost(struct Curl_easy *data) +{ + int sshkeytype = 0; + size_t keylen = 0; + int rc = 0; + CURLcode result = CURLE_OK; + +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + if(data->set.str[STRING_SSH_KNOWNHOSTS]) { + /* we're asked to verify the host against a file */ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + struct libssh2_knownhost *host = NULL; + const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, + &keylen, &sshkeytype); + int keycheck = LIBSSH2_KNOWNHOST_CHECK_FAILURE; + int keybit = 0; + + if(remotekey) { + /* + * A subject to figure out is what host name we need to pass in here. + * What host name does OpenSSH store in its file if an IDN name is + * used? + */ + enum curl_khmatch keymatch; + curl_sshkeycallback func = + data->set.ssh_keyfunc ? data->set.ssh_keyfunc : sshkeycallback; + struct curl_khkey knownkey; + struct curl_khkey *knownkeyp = NULL; + struct curl_khkey foundkey; + + switch(sshkeytype) { + case LIBSSH2_HOSTKEY_TYPE_RSA: + keybit = LIBSSH2_KNOWNHOST_KEY_SSHRSA; + break; + case LIBSSH2_HOSTKEY_TYPE_DSS: + keybit = LIBSSH2_KNOWNHOST_KEY_SSHDSS; + break; +#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 + case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: + keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_256; + break; +#endif +#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_384 + case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: + keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_384; + break; +#endif +#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_521 + case LIBSSH2_HOSTKEY_TYPE_ECDSA_521: + keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_521; + break; +#endif +#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 + case LIBSSH2_HOSTKEY_TYPE_ED25519: + keybit = LIBSSH2_KNOWNHOST_KEY_ED25519; + break; +#endif + default: + infof(data, "unsupported key type, can't check knownhosts"); + keybit = 0; + break; + } + if(!keybit) + /* no check means failure! */ + rc = CURLKHSTAT_REJECT; + else { +#ifdef HAVE_LIBSSH2_KNOWNHOST_CHECKP + keycheck = libssh2_knownhost_checkp(sshc->kh, + conn->host.name, + (conn->remote_port != PORT_SSH)? + conn->remote_port:-1, + remotekey, keylen, + LIBSSH2_KNOWNHOST_TYPE_PLAIN| + LIBSSH2_KNOWNHOST_KEYENC_RAW| + keybit, + &host); +#else + keycheck = libssh2_knownhost_check(sshc->kh, + conn->host.name, + remotekey, keylen, + LIBSSH2_KNOWNHOST_TYPE_PLAIN| + LIBSSH2_KNOWNHOST_KEYENC_RAW| + keybit, + &host); +#endif + + infof(data, "SSH host check: %d, key: %s", keycheck, + (keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)? + host->key:"<none>"); + + /* setup 'knownkey' */ + if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { + knownkey.key = host->key; + knownkey.len = 0; + knownkey.keytype = convert_ssh2_keytype(sshkeytype); + knownkeyp = &knownkey; + } + + /* setup 'foundkey' */ + foundkey.key = remotekey; + foundkey.len = keylen; + foundkey.keytype = convert_ssh2_keytype(sshkeytype); + + /* + * if any of the LIBSSH2_KNOWNHOST_CHECK_* defines and the + * curl_khmatch enum are ever modified, we need to introduce a + * translation table here! + */ + keymatch = (enum curl_khmatch)keycheck; + + /* Ask the callback how to behave */ + Curl_set_in_callback(data, true); + 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); + } + } + else + /* no remotekey means failure! */ + rc = CURLKHSTAT_REJECT; + + switch(rc) { + default: /* unknown return codes will equal reject */ + /* FALLTHROUGH */ + case CURLKHSTAT_REJECT: + state(data, SSH_SESSION_FREE); + /* FALLTHROUGH */ + case CURLKHSTAT_DEFER: + /* DEFER means bail out but keep the SSH_HOSTKEY state */ + result = sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + break; + case CURLKHSTAT_FINE_REPLACE: + /* remove old host+key that doesn't match */ + if(host) + libssh2_knownhost_del(sshc->kh, host); + /* FALLTHROUGH */ + case CURLKHSTAT_FINE: + /* FALLTHROUGH */ + case CURLKHSTAT_FINE_ADD_TO_FILE: + /* proceed */ + if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) { + /* the found host+key didn't match but has been told to be fine + anyway so we add it in memory */ + int addrc = libssh2_knownhost_add(sshc->kh, + conn->host.name, NULL, + remotekey, keylen, + LIBSSH2_KNOWNHOST_TYPE_PLAIN| + LIBSSH2_KNOWNHOST_KEYENC_RAW| + keybit, NULL); + if(addrc) + infof(data, "WARNING: adding the known host %s failed", + conn->host.name); + else if(rc == CURLKHSTAT_FINE_ADD_TO_FILE || + rc == CURLKHSTAT_FINE_REPLACE) { + /* 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); + if(wrc) { + infof(data, "WARNING: writing %s failed", + data->set.str[STRING_SSH_KNOWNHOSTS]); + } + } + } + break; + } + } +#else /* HAVE_LIBSSH2_KNOWNHOST_API */ + (void)data; +#endif + return result; +} + +static CURLcode ssh_check_fingerprint(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.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]; + + infof(data, "SSH MD5 public key: %s", + pubkey_md5 != NULL ? pubkey_md5 : "NULL"); + infof(data, "SSH SHA256 public key: %s", + pubkey_sha256 != NULL ? pubkey_sha256 : "NULL"); + + if(pubkey_sha256) { + const char *fingerprint = NULL; + char *fingerprint_b64 = NULL; + size_t fingerprint_b64_len; + size_t pub_pos = 0; + size_t b64_pos = 0; + +#ifdef LIBSSH2_HOSTKEY_HASH_SHA256 + /* The fingerprint points to static storage (!), don't free() it. */ + fingerprint = libssh2_hostkey_hash(sshc->ssh_session, + LIBSSH2_HOSTKEY_HASH_SHA256); +#else + const char *hostkey; + size_t len = 0; + unsigned char hash[32]; + + hostkey = libssh2_session_hostkey(sshc->ssh_session, &len, NULL); + if(hostkey) { + if(!Curl_sha256it(hash, (const unsigned char *) hostkey, len)) + fingerprint = (char *) hash; + } +#endif + + if(!fingerprint) { + failf(data, + "Denied establishing ssh session: sha256 fingerprint " + "not available"); + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + + /* The length of fingerprint is 32 bytes for SHA256. + * See libssh2_hostkey_hash documentation. */ + if(Curl_base64_encode(fingerprint, 32, &fingerprint_b64, + &fingerprint_b64_len) != CURLE_OK) { + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + + if(!fingerprint_b64) { + failf(data, "sha256 fingerprint could not be encoded"); + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + + infof(data, "SSH SHA256 fingerprint: %s", fingerprint_b64); + + /* Find the position of any = padding characters in the public key */ + while((pubkey_sha256[pub_pos] != '=') && pubkey_sha256[pub_pos]) { + pub_pos++; + } + + /* Find the position of any = padding characters in the base64 coded + * hostkey fingerprint */ + while((fingerprint_b64[b64_pos] != '=') && fingerprint_b64[b64_pos]) { + b64_pos++; + } + + /* Before we authenticate we check the hostkey's sha256 fingerprint + * against a known fingerprint, if available. + */ + if((pub_pos != b64_pos) || + strncmp(fingerprint_b64, pubkey_sha256, pub_pos)) { + failf(data, + "Denied establishing ssh session: mismatch sha256 fingerprint. " + "Remote %s is not equal to %s", fingerprint_b64, pubkey_sha256); + free(fingerprint_b64); + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + + free(fingerprint_b64); + + infof(data, "SHA256 checksum match"); + } + + if(pubkey_md5) { + char md5buffer[33]; + const char *fingerprint = NULL; + + fingerprint = libssh2_hostkey_hash(sshc->ssh_session, + LIBSSH2_HOSTKEY_HASH_MD5); + + if(fingerprint) { + /* The fingerprint points to static storage (!), don't free() it. */ + int i; + for(i = 0; i < 16; i++) { + msnprintf(&md5buffer[i*2], 3, "%02x", (unsigned char) fingerprint[i]); + } + + infof(data, "SSH MD5 fingerprint: %s", md5buffer); + } + + /* This does NOT verify the length of 'pubkey_md5' separately, which will + make the comparison below fail unless it is exactly 32 characters */ + if(!fingerprint || !strcasecompare(md5buffer, pubkey_md5)) { + if(fingerprint) { + failf(data, + "Denied establishing ssh session: mismatch md5 fingerprint. " + "Remote %s is not equal to %s", md5buffer, pubkey_md5); + } + else { + failf(data, + "Denied establishing ssh session: md5 fingerprint " + "not available"); + } + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + infof(data, "MD5 checksum match"); + } + + if(!pubkey_md5 && !pubkey_sha256) { + if(data->set.ssh_hostkeyfunc) { + size_t keylen = 0; + int sshkeytype = 0; + int rc = 0; + /* we handle the process to the callback */ + const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, + &keylen, &sshkeytype); + if(remotekey) { + int keytype = convert_ssh2_keytype(sshkeytype); + Curl_set_in_callback(data, true); + rc = data->set.ssh_hostkeyfunc(data->set.ssh_hostkeyfunc_userp, + keytype, remotekey, keylen); + Curl_set_in_callback(data, false); + if(rc!= CURLKHMATCH_OK) { + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + } + else { + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + return CURLE_OK; + } + else { + return ssh_knownhost(data); + } + } + else { + /* as we already matched, we skip the check for known hosts */ + return CURLE_OK; + } +} + +/* + * ssh_force_knownhost_key_type() will check the known hosts file and try to + * force a specific public key type from the server if an entry is found. + */ +static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + +#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519 + static const char * const hostkey_method_ssh_ed25519 + = "ssh-ed25519"; +#endif +#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_521 + static const char * const hostkey_method_ssh_ecdsa_521 + = "ecdsa-sha2-nistp521"; +#endif +#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_384 + static const char * const hostkey_method_ssh_ecdsa_384 + = "ecdsa-sha2-nistp384"; +#endif +#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256 + static const char * const hostkey_method_ssh_ecdsa_256 + = "ecdsa-sha2-nistp256"; +#endif + static const char * const hostkey_method_ssh_rsa + = "ssh-rsa"; + static const char * const hostkey_method_ssh_rsa_all + = "rsa-sha2-256,rsa-sha2-512,ssh-rsa"; + static const char * const hostkey_method_ssh_dss + = "ssh-dss"; + + const char *hostkey_method = NULL; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + struct libssh2_knownhost* store = NULL; + const char *kh_name_end = NULL; + size_t kh_name_size = 0; + int port = 0; + bool found = false; + + if(sshc->kh && !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) { + /* lets try to find our host in the known hosts file */ + while(!libssh2_knownhost_get(sshc->kh, &store, store)) { + /* For non-standard ports, the name will be enclosed in */ + /* square brackets, followed by a colon and the port */ + if(store) { + if(store->name) { + if(store->name[0] == '[') { + kh_name_end = strstr(store->name, "]:"); + if(!kh_name_end) { + infof(data, "Invalid host pattern %s in %s", + store->name, data->set.str[STRING_SSH_KNOWNHOSTS]); + continue; + } + port = atoi(kh_name_end + 2); + if(kh_name_end && (port == conn->remote_port)) { + kh_name_size = strlen(store->name) - 1 - strlen(kh_name_end); + if(strncmp(store->name + 1, + conn->host.name, kh_name_size) == 0) { + found = true; + break; + } + } + } + else if(strcmp(store->name, conn->host.name) == 0) { + found = true; + break; + } + } + else { + found = true; + break; + } + } + } + + if(found) { + int rc; + infof(data, "Found host %s in %s", + conn->host.name, data->set.str[STRING_SSH_KNOWNHOSTS]); + + switch(store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) { +#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519 + case LIBSSH2_KNOWNHOST_KEY_ED25519: + hostkey_method = hostkey_method_ssh_ed25519; + break; +#endif +#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_521 + case LIBSSH2_KNOWNHOST_KEY_ECDSA_521: + hostkey_method = hostkey_method_ssh_ecdsa_521; + break; +#endif +#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_384 + case LIBSSH2_KNOWNHOST_KEY_ECDSA_384: + hostkey_method = hostkey_method_ssh_ecdsa_384; + break; +#endif +#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256 + case LIBSSH2_KNOWNHOST_KEY_ECDSA_256: + hostkey_method = hostkey_method_ssh_ecdsa_256; + break; +#endif + case LIBSSH2_KNOWNHOST_KEY_SSHRSA: +#ifdef HAVE_LIBSSH2_VERSION + if(libssh2_version(0x010900)) + /* since 1.9.0 libssh2_session_method_pref() works as expected */ + hostkey_method = hostkey_method_ssh_rsa_all; + else +#endif + /* old libssh2 which cannot correctly remove unsupported methods due + * to bug in src/kex.c or does not support the new methods anyways. + */ + hostkey_method = hostkey_method_ssh_rsa; + break; + case LIBSSH2_KNOWNHOST_KEY_SSHDSS: + hostkey_method = hostkey_method_ssh_dss; + break; + case LIBSSH2_KNOWNHOST_KEY_RSA1: + failf(data, "Found host key type RSA1 which is not supported"); + return CURLE_SSH; + default: + failf(data, "Unknown host key type: %i", + (store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK)); + return CURLE_SSH; + } + + infof(data, "Set \"%s\" as SSH hostkey type", hostkey_method); + rc = libssh2_session_method_pref(sshc->ssh_session, + LIBSSH2_METHOD_HOSTKEY, hostkey_method); + if(rc) { + char *errmsg = NULL; + int errlen; + libssh2_session_last_error(sshc->ssh_session, &errmsg, &errlen, 0); + failf(data, "libssh2: %s", errmsg); + result = libssh2_session_error_to_CURLE(rc); + } + } + else { + infof(data, "Did not find host %s in %s", + conn->host.name, data->set.str[STRING_SSH_KNOWNHOSTS]); + } + } + +#endif /* HAVE_LIBSSH2_KNOWNHOST_API */ + + return result; +} + +/* + * ssh_statemach_act() runs the SSH state machine as far as it can without + * blocking and without reaching the end. The data the pointer 'block' points + * to will be set to TRUE if the libssh2 function returns LIBSSH2_ERROR_EAGAIN + * meaning it wants to be called again when the socket is ready + */ + +static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct SSHPROTO *sshp = data->req.p.ssh; + struct ssh_conn *sshc = &conn->proto.sshc; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int rc = LIBSSH2_ERROR_NONE; + int ssherr; + unsigned long sftperr; + int seekerr = CURL_SEEKFUNC_OK; + size_t readdir_len; + *block = 0; /* we're not blocking by default */ + + do { + switch(sshc->state) { + case SSH_INIT: + sshc->secondCreateDirs = 0; + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_OK; + + /* Set libssh2 to non-blocking, since everything internally is + non-blocking */ + libssh2_session_set_blocking(sshc->ssh_session, 0); + + result = ssh_force_knownhost_key_type(data); + if(result) { + state(data, SSH_SESSION_FREE); + sshc->actualcode = result; + break; + } + + state(data, SSH_S_STARTUP); + /* FALLTHROUGH */ + + case SSH_S_STARTUP: + rc = session_startup(sshc->ssh_session, sock); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0); + failf(data, "Failure establishing ssh session: %d, %s", rc, err_msg); + + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_FAILED_INIT; + break; + } + + state(data, SSH_HOSTKEY); + + /* FALLTHROUGH */ + case SSH_HOSTKEY: + /* + * Before we authenticate we should check the hostkey's fingerprint + * against our known hosts. How that is handled (reading from file, + * whatever) is up to us. + */ + result = ssh_check_fingerprint(data); + if(!result) + state(data, SSH_AUTHLIST); + /* ssh_check_fingerprint sets state appropriately on error */ + break; + + case SSH_AUTHLIST: + /* + * Figure out authentication methods + * NB: As soon as we have provided a username to an openssh server we + * must never change it later. Thus, always specify the correct username + * here, even though the libssh2 docs kind of indicate that it should be + * possible to get a 'generic' list (not user-specific) of authentication + * methods, presumably with a blank username. That won't work in my + * experience. + * So always specify it here. + */ + sshc->authlist = libssh2_userauth_list(sshc->ssh_session, + conn->user, + curlx_uztoui(strlen(conn->user))); + + if(!sshc->authlist) { + if(libssh2_userauth_authenticated(sshc->ssh_session)) { + sshc->authed = TRUE; + infof(data, "SSH user accepted with no authentication"); + state(data, SSH_AUTH_DONE); + break; + } + ssherr = libssh2_session_last_errno(sshc->ssh_session); + if(ssherr == LIBSSH2_ERROR_EAGAIN) + rc = LIBSSH2_ERROR_EAGAIN; + else { + state(data, SSH_SESSION_FREE); + sshc->actualcode = libssh2_session_error_to_CURLE(ssherr); + } + break; + } + infof(data, "SSH authentication methods available: %s", + sshc->authlist); + + state(data, SSH_AUTH_PKEY_INIT); + break; + + case SSH_AUTH_PKEY_INIT: + /* + * Check the supported auth types in the order I feel is most secure + * with the requested type of authentication + */ + sshc->authed = FALSE; + + if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && + (strstr(sshc->authlist, "publickey") != NULL)) { + bool out_of_memory = FALSE; + + sshc->rsa_pub = sshc->rsa = NULL; + + if(data->set.str[STRING_SSH_PRIVATE_KEY]) + sshc->rsa = strdup(data->set.str[STRING_SSH_PRIVATE_KEY]); + else { + /* To ponder about: should really the lib be messing about with the + HOME environment variable etc? */ + char *home = curl_getenv("HOME"); + + /* If no private key file is specified, try some common paths. */ + if(home) { + /* Try ~/.ssh first. */ + sshc->rsa = aprintf("%s/.ssh/id_rsa", home); + if(!sshc->rsa) + out_of_memory = TRUE; + else if(access(sshc->rsa, R_OK) != 0) { + Curl_safefree(sshc->rsa); + sshc->rsa = aprintf("%s/.ssh/id_dsa", home); + if(!sshc->rsa) + out_of_memory = TRUE; + else if(access(sshc->rsa, R_OK) != 0) { + Curl_safefree(sshc->rsa); + } + } + free(home); + } + if(!out_of_memory && !sshc->rsa) { + /* Nothing found; try the current dir. */ + sshc->rsa = strdup("id_rsa"); + if(sshc->rsa && access(sshc->rsa, R_OK) != 0) { + Curl_safefree(sshc->rsa); + sshc->rsa = strdup("id_dsa"); + if(sshc->rsa && access(sshc->rsa, R_OK) != 0) { + Curl_safefree(sshc->rsa); + /* Out of guesses. Set to the empty string to avoid + * surprising info messages. */ + sshc->rsa = 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 simply passing sshc->rsa_pub = NULL. + */ + if(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 = strdup(data->set.str[STRING_SSH_PUBLIC_KEY]); + if(!sshc->rsa_pub) + out_of_memory = TRUE; + } + + if(out_of_memory || !sshc->rsa) { + Curl_safefree(sshc->rsa); + Curl_safefree(sshc->rsa_pub); + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + + sshc->passphrase = data->set.ssl.key_passwd; + if(!sshc->passphrase) + sshc->passphrase = ""; + + if(sshc->rsa_pub) + infof(data, "Using SSH public key file '%s'", sshc->rsa_pub); + infof(data, "Using SSH private key file '%s'", sshc->rsa); + + state(data, SSH_AUTH_PKEY); + } + else { + state(data, SSH_AUTH_PASS_INIT); + } + break; + + case SSH_AUTH_PKEY: + /* The function below checks if the files exists, no need to stat() here. + */ + rc = libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session, + conn->user, + curlx_uztoui( + strlen(conn->user)), + sshc->rsa_pub, + sshc->rsa, sshc->passphrase); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + + Curl_safefree(sshc->rsa_pub); + Curl_safefree(sshc->rsa); + + if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized SSH public key authentication"); + state(data, SSH_AUTH_DONE); + } + else { + char *err_msg = NULL; + char unknown[] = "Reason unknown (-1)"; + if(rc == -1) { + /* No error message has been set and the last set error message, if + any, is from a previous error so ignore it. #11837 */ + err_msg = unknown; + } + else { + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + } + infof(data, "SSH public key authentication failed: %s", err_msg); + state(data, SSH_AUTH_PASS_INIT); + rc = 0; /* clear rc and continue */ + } + break; + + case SSH_AUTH_PASS_INIT: + if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) && + (strstr(sshc->authlist, "password") != NULL)) { + state(data, SSH_AUTH_PASS); + } + else { + state(data, SSH_AUTH_HOST_INIT); + rc = 0; /* clear rc and continue */ + } + break; + + case SSH_AUTH_PASS: + rc = libssh2_userauth_password_ex(sshc->ssh_session, conn->user, + curlx_uztoui(strlen(conn->user)), + conn->passwd, + curlx_uztoui(strlen(conn->passwd)), + NULL); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized password authentication"); + state(data, SSH_AUTH_DONE); + } + else { + state(data, SSH_AUTH_HOST_INIT); + rc = 0; /* clear rc and continue */ + } + break; + + case SSH_AUTH_HOST_INIT: + if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && + (strstr(sshc->authlist, "hostbased") != NULL)) { + state(data, SSH_AUTH_HOST); + } + else { + state(data, SSH_AUTH_AGENT_INIT); + } + break; + + case SSH_AUTH_HOST: + state(data, SSH_AUTH_AGENT_INIT); + break; + + case SSH_AUTH_AGENT_INIT: +#ifdef HAVE_LIBSSH2_AGENT_API + if((data->set.ssh_auth_types & CURLSSH_AUTH_AGENT) + && (strstr(sshc->authlist, "publickey") != NULL)) { + + /* Connect to the ssh-agent */ + /* The agent could be shared by a curl thread i believe + but nothing obvious as keys can be added/removed at any time */ + if(!sshc->ssh_agent) { + sshc->ssh_agent = libssh2_agent_init(sshc->ssh_session); + if(!sshc->ssh_agent) { + infof(data, "Could not create agent object"); + + state(data, SSH_AUTH_KEY_INIT); + break; + } + } + + rc = libssh2_agent_connect(sshc->ssh_agent); + if(rc == LIBSSH2_ERROR_EAGAIN) + break; + if(rc < 0) { + infof(data, "Failure connecting to agent"); + state(data, SSH_AUTH_KEY_INIT); + rc = 0; /* clear rc and continue */ + } + else { + state(data, SSH_AUTH_AGENT_LIST); + } + } + else +#endif /* HAVE_LIBSSH2_AGENT_API */ + state(data, SSH_AUTH_KEY_INIT); + break; + + case SSH_AUTH_AGENT_LIST: +#ifdef HAVE_LIBSSH2_AGENT_API + rc = libssh2_agent_list_identities(sshc->ssh_agent); + + if(rc == LIBSSH2_ERROR_EAGAIN) + break; + if(rc < 0) { + infof(data, "Failure requesting identities to agent"); + state(data, SSH_AUTH_KEY_INIT); + rc = 0; /* clear rc and continue */ + } + else { + state(data, SSH_AUTH_AGENT); + sshc->sshagent_prev_identity = NULL; + } +#endif + break; + + case SSH_AUTH_AGENT: +#ifdef HAVE_LIBSSH2_AGENT_API + /* as prev_identity evolves only after an identity user auth finished we + can safely request it again as long as EAGAIN is returned here or by + libssh2_agent_userauth */ + rc = libssh2_agent_get_identity(sshc->ssh_agent, + &sshc->sshagent_identity, + sshc->sshagent_prev_identity); + if(rc == LIBSSH2_ERROR_EAGAIN) + break; + + if(rc == 0) { + rc = libssh2_agent_userauth(sshc->ssh_agent, conn->user, + sshc->sshagent_identity); + + if(rc < 0) { + if(rc != LIBSSH2_ERROR_EAGAIN) { + /* tried and failed? go to next identity */ + sshc->sshagent_prev_identity = sshc->sshagent_identity; + } + break; + } + } + + if(rc < 0) + infof(data, "Failure requesting identities to agent"); + else if(rc == 1) + infof(data, "No identity would match"); + + if(rc == LIBSSH2_ERROR_NONE) { + sshc->authed = TRUE; + infof(data, "Agent based authentication successful"); + state(data, SSH_AUTH_DONE); + } + else { + state(data, SSH_AUTH_KEY_INIT); + rc = 0; /* clear rc and continue */ + } +#endif + break; + + case SSH_AUTH_KEY_INIT: + if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) + && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) { + state(data, SSH_AUTH_KEY); + } + else { + state(data, SSH_AUTH_DONE); + } + break; + + case SSH_AUTH_KEY: + /* Authentication failed. Continue with keyboard-interactive now. */ + rc = libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session, + conn->user, + curlx_uztoui( + strlen(conn->user)), + &kbd_callback); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized keyboard interactive authentication"); + } + state(data, SSH_AUTH_DONE); + break; + + case SSH_AUTH_DONE: + if(!sshc->authed) { + failf(data, "Authentication failure"); + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_LOGIN_DENIED; + break; + } + + /* + * At this point we have an authenticated ssh session. + */ + infof(data, "Authentication complete"); + + Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSH is connected */ + + conn->sockfd = sock; + conn->writesockfd = CURL_SOCKET_BAD; + + if(conn->handler->protocol == CURLPROTO_SFTP) { + state(data, SSH_SFTP_INIT); + break; + } + infof(data, "SSH CONNECT phase done"); + state(data, SSH_STOP); + break; + + case SSH_SFTP_INIT: + /* + * Start the libssh2 sftp session + */ + sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session); + if(!sshc->sftp_session) { + char *err_msg = NULL; + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + failf(data, "Failure initializing sftp session: %s", err_msg); + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_FAILED_INIT; + break; + } + state(data, SSH_SFTP_REALPATH); + break; + + case SSH_SFTP_REALPATH: + { + char tempHome[PATH_MAX]; + + /* + * Get the "home" directory + */ + rc = sftp_libssh2_realpath(sshc->sftp_session, ".", + tempHome, PATH_MAX-1); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc > 0) { + /* It seems that this string is not always NULL terminated */ + tempHome[rc] = '\0'; + sshc->homedir = strdup(tempHome); + if(!sshc->homedir) { + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + data->state.most_recent_ftp_entrypath = sshc->homedir; + } + else { + /* Return the error type */ + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + if(sftperr) + result = sftp_libssh2_error_to_CURLE(sftperr); + else + /* in this case, the error wasn't in the SFTP level but for example + a time-out or similar */ + result = CURLE_SSH; + sshc->actualcode = result; + DEBUGF(infof(data, "error = %lu makes libcurl = %d", + sftperr, (int)result)); + state(data, SSH_STOP); + break; + } + } + /* This is the last step in the SFTP connect phase. Do note that while + we get the homedir here, we get the "workingpath" in the DO action + since the homedir will remain the same between request but the + working path will not. */ + DEBUGF(infof(data, "SSH CONNECT phase done")); + state(data, SSH_STOP); + break; + + case SSH_SFTP_QUOTE_INIT: + + result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); + if(result) { + sshc->actualcode = result; + state(data, SSH_STOP); + break; + } + + if(data->set.quote) { + infof(data, "Sending quote commands"); + sshc->quote_item = data->set.quote; + state(data, SSH_SFTP_QUOTE); + } + else { + state(data, SSH_SFTP_GETINFO); + } + break; + + case SSH_SFTP_POSTQUOTE_INIT: + if(data->set.postquote) { + infof(data, "Sending quote commands"); + sshc->quote_item = data->set.postquote; + state(data, SSH_SFTP_QUOTE); + } + else { + state(data, SSH_STOP); + } + break; + + case SSH_SFTP_QUOTE: + /* Send any quote commands */ + { + const char *cp; + + /* + * Support some of the "FTP" commands + * + * 'sshc->quote_item' is already verified to be non-NULL before it + * switched to this state. + */ + char *cmd = sshc->quote_item->data; + sshc->acceptfail = FALSE; + + /* if a command starts with an asterisk, which a legal SFTP command never + can, the command will be allowed to fail without it causing any + aborts or cancels etc. It will cause libcurl to act as if the command + is successful, whatever the server responds. */ + + if(cmd[0] == '*') { + cmd++; + sshc->acceptfail = TRUE; + } + + if(strcasecompare("pwd", cmd)) { + /* output debug output if that is requested */ + char *tmp = aprintf("257 \"%s\" is current directory.\n", + sshp->path); + if(!tmp) { + result = CURLE_OUT_OF_MEMORY; + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + break; + } + Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4); + Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp)); + + /* this sends an FTP-like "header" to the header callback so that the + current directory can be read very similar to how it is read when + using ordinary FTP. */ + result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); + free(tmp); + if(result) { + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + } + else + state(data, SSH_SFTP_NEXT_QUOTE); + break; + } + + /* + * the arguments following the command must be separated from the + * command with a space so we can check for it unconditionally + */ + cp = strchr(cmd, ' '); + if(!cp) { + failf(data, "Syntax error command '%s', missing parameter", + cmd); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + + /* + * also, every command takes at least one argument so we get that + * first argument right now + */ + result = Curl_get_pathname(&cp, &sshc->quote_path1, sshc->homedir); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error: Bad first parameter to '%s'", cmd); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + break; + } + + /* + * SFTP is a binary protocol, so we don't send text commands + * to the server. Instead, we scan for commands used by + * OpenSSH's sftp program and call the appropriate libssh2 + * functions. + */ + if(strncasecompare(cmd, "chgrp ", 6) || + strncasecompare(cmd, "chmod ", 6) || + strncasecompare(cmd, "chown ", 6) || + strncasecompare(cmd, "atime ", 6) || + strncasecompare(cmd, "mtime ", 6)) { + /* attribute change */ + + /* sshc->quote_path1 contains the mode to set */ + /* get the destination */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error in %s: Bad second parameter", cmd); + Curl_safefree(sshc->quote_path1); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + break; + } + memset(&sshp->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + state(data, SSH_SFTP_QUOTE_STAT); + break; + } + if(strncasecompare(cmd, "ln ", 3) || + strncasecompare(cmd, "symlink ", 8)) { + /* symbolic linking */ + /* sshc->quote_path1 is the source */ + /* get the destination */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, + "Syntax error in ln/symlink: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + break; + } + state(data, SSH_SFTP_QUOTE_SYMLINK); + break; + } + else if(strncasecompare(cmd, "mkdir ", 6)) { + /* create dir */ + state(data, SSH_SFTP_QUOTE_MKDIR); + break; + } + else if(strncasecompare(cmd, "rename ", 7)) { + /* rename file */ + /* first param is the source path */ + /* second param is the dest. path */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error in rename: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + break; + } + state(data, SSH_SFTP_QUOTE_RENAME); + break; + } + else if(strncasecompare(cmd, "rmdir ", 6)) { + /* delete dir */ + state(data, SSH_SFTP_QUOTE_RMDIR); + break; + } + else if(strncasecompare(cmd, "rm ", 3)) { + state(data, SSH_SFTP_QUOTE_UNLINK); + break; + } +#ifdef HAS_STATVFS_SUPPORT + else if(strncasecompare(cmd, "statvfs ", 8)) { + state(data, SSH_SFTP_QUOTE_STATVFS); + break; + } +#endif + + failf(data, "Unknown SFTP command"); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + + case SSH_SFTP_NEXT_QUOTE: + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + + sshc->quote_item = sshc->quote_item->next; + + if(sshc->quote_item) { + state(data, SSH_SFTP_QUOTE); + } + else { + if(sshc->nextstate != SSH_NO_STATE) { + state(data, sshc->nextstate); + sshc->nextstate = SSH_NO_STATE; + } + else { + state(data, SSH_SFTP_GETINFO); + } + } + break; + + case SSH_SFTP_QUOTE_STAT: + { + char *cmd = sshc->quote_item->data; + sshc->acceptfail = FALSE; + + /* if a command starts with an asterisk, which a legal SFTP command never + can, the command will be allowed to fail without it causing any + aborts or cancels etc. It will cause libcurl to act as if the command + is successful, whatever the server responds. */ + + if(cmd[0] == '*') { + cmd++; + sshc->acceptfail = TRUE; + } + + if(!strncasecompare(cmd, "chmod", 5)) { + /* Since chown and chgrp only set owner OR group but libssh2 wants to + * set them both at once, we need to obtain the current ownership + * first. This takes an extra protocol round trip. + */ + rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_STAT, + &sshp->quote_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc && !sshc->acceptfail) { /* get those attributes */ + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Attempt to get SFTP stats failed: %s", + sftp_libssh2_strerror(sftperr)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + } + + /* Now set the new attributes... */ + if(strncasecompare(cmd, "chgrp", 5)) { + sshp->quote_attrs.gid = strtoul(sshc->quote_path1, NULL, 10); + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; + if(sshp->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && + !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Syntax error: chgrp gid not a number"); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + } + else if(strncasecompare(cmd, "chmod", 5)) { + sshp->quote_attrs.permissions = strtoul(sshc->quote_path1, NULL, 8); + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; + /* permissions are octal */ + if(sshp->quote_attrs.permissions == 0 && + !ISDIGIT(sshc->quote_path1[0])) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Syntax error: chmod permissions not a number"); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + } + else if(strncasecompare(cmd, "chown", 5)) { + sshp->quote_attrs.uid = strtoul(sshc->quote_path1, NULL, 10); + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; + if(sshp->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && + !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Syntax error: chown uid not a number"); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + } + else if(strncasecompare(cmd, "atime", 5) || + strncasecompare(cmd, "mtime", 5)) { + time_t date = Curl_getdate_capped(sshc->quote_path1); + bool fail = FALSE; + + if(date == -1) { + failf(data, "incorrect date format for %.*s", 5, cmd); + fail = TRUE; + } +#if SIZEOF_TIME_T > SIZEOF_LONG + if(date > 0xffffffff) { + /* if 'long' can't old >32bit, this date cannot be sent */ + failf(data, "date overflow"); + fail = TRUE; + } +#endif + if(fail) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + if(strncasecompare(cmd, "atime", 5)) + sshp->quote_attrs.atime = (unsigned long)date; + else /* mtime */ + sshp->quote_attrs.mtime = (unsigned long)date; + + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_ACMODTIME; + } + + /* Now send the completed structure... */ + state(data, SSH_SFTP_QUOTE_SETSTAT); + break; + } + + case SSH_SFTP_QUOTE_SETSTAT: + rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_SETSTAT, + &sshp->quote_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc && !sshc->acceptfail) { + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Attempt to set SFTP stats failed: %s", + sftp_libssh2_strerror(sftperr)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_SYMLINK: + rc = libssh2_sftp_symlink_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_SYMLINK); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc && !sshc->acceptfail) { + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "symlink command failed: %s", + sftp_libssh2_strerror(sftperr)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_MKDIR: + rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + data->set.new_directory_perms); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc && !sshc->acceptfail) { + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + failf(data, "mkdir command failed: %s", + sftp_libssh2_strerror(sftperr)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_RENAME: + rc = libssh2_sftp_rename_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_RENAME_OVERWRITE | + LIBSSH2_SFTP_RENAME_ATOMIC | + LIBSSH2_SFTP_RENAME_NATIVE); + + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc && !sshc->acceptfail) { + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "rename command failed: %s", + sftp_libssh2_strerror(sftperr)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_RMDIR: + rc = libssh2_sftp_rmdir_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1))); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc && !sshc->acceptfail) { + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + failf(data, "rmdir command failed: %s", + sftp_libssh2_strerror(sftperr)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_UNLINK: + rc = libssh2_sftp_unlink_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1))); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc && !sshc->acceptfail) { + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + failf(data, "rm command failed: %s", sftp_libssh2_strerror(sftperr)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + +#ifdef HAS_STATVFS_SUPPORT + case SSH_SFTP_QUOTE_STATVFS: + { + LIBSSH2_SFTP_STATVFS statvfs; + rc = libssh2_sftp_statvfs(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + &statvfs); + + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc && !sshc->acceptfail) { + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + failf(data, "statvfs command failed: %s", + sftp_libssh2_strerror(sftperr)); + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + else if(rc == 0) { + #ifdef _MSC_VER + #define LIBSSH2_VFS_SIZE_MASK "I64u" + #else + #define LIBSSH2_VFS_SIZE_MASK "llu" + #endif + char *tmp = aprintf("statvfs:\n" + "f_bsize: %" LIBSSH2_VFS_SIZE_MASK "\n" + "f_frsize: %" LIBSSH2_VFS_SIZE_MASK "\n" + "f_blocks: %" LIBSSH2_VFS_SIZE_MASK "\n" + "f_bfree: %" LIBSSH2_VFS_SIZE_MASK "\n" + "f_bavail: %" LIBSSH2_VFS_SIZE_MASK "\n" + "f_files: %" LIBSSH2_VFS_SIZE_MASK "\n" + "f_ffree: %" LIBSSH2_VFS_SIZE_MASK "\n" + "f_favail: %" LIBSSH2_VFS_SIZE_MASK "\n" + "f_fsid: %" LIBSSH2_VFS_SIZE_MASK "\n" + "f_flag: %" LIBSSH2_VFS_SIZE_MASK "\n" + "f_namemax: %" LIBSSH2_VFS_SIZE_MASK "\n", + statvfs.f_bsize, statvfs.f_frsize, + statvfs.f_blocks, statvfs.f_bfree, + statvfs.f_bavail, statvfs.f_files, + statvfs.f_ffree, statvfs.f_favail, + statvfs.f_fsid, statvfs.f_flag, + statvfs.f_namemax); + if(!tmp) { + result = CURLE_OUT_OF_MEMORY; + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + break; + } + + result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); + free(tmp); + if(result) { + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + } + } + state(data, SSH_SFTP_NEXT_QUOTE); + break; + } +#endif + case SSH_SFTP_GETINFO: + { + if(data->set.get_filetime) { + state(data, SSH_SFTP_FILETIME); + } + else { + state(data, SSH_SFTP_TRANS_INIT); + } + break; + } + + case SSH_SFTP_FILETIME: + { + LIBSSH2_SFTP_ATTRIBUTES attrs; + + rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + LIBSSH2_SFTP_STAT, &attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc == 0) { + data->info.filetime = attrs.mtime; + } + + state(data, SSH_SFTP_TRANS_INIT); + break; + } + + case SSH_SFTP_TRANS_INIT: + if(data->state.upload) + state(data, SSH_SFTP_UPLOAD_INIT); + else { + if(sshp->path[strlen(sshp->path)-1] == '/') + state(data, SSH_SFTP_READDIR_INIT); + else + state(data, SSH_SFTP_DOWNLOAD_INIT); + } + break; + + case SSH_SFTP_UPLOAD_INIT: + { + unsigned long flags; + /* + * NOTE!!! libssh2 requires that the destination path is a full path + * that includes the destination file and name OR ends in a "/" + * If this is not done the destination file will be named the + * same name as the last directory in the path. + */ + + if(data->state.resume_from) { + LIBSSH2_SFTP_ATTRIBUTES attrs; + if(data->state.resume_from < 0) { + rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + LIBSSH2_SFTP_STAT, &attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc) { + data->state.resume_from = 0; + } + else { + curl_off_t size = attrs.filesize; + if(size < 0) { + failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + data->state.resume_from = attrs.filesize; + } + } + } + + if(data->set.remote_append) + /* Try to open for append, but create if nonexisting */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_APPEND; + else if(data->state.resume_from > 0) + /* If we have restart position then open for append */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_APPEND; + else + /* Clear file before writing (normal behavior) */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC; + + sshc->sftp_handle = + libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + flags, data->set.new_file_perms, + LIBSSH2_SFTP_OPENFILE); + + if(!sshc->sftp_handle) { + rc = libssh2_session_last_errno(sshc->ssh_session); + + if(LIBSSH2_ERROR_EAGAIN == rc) + break; + + if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc) + /* only when there was an SFTP protocol error can we extract + the sftp error! */ + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + else + sftperr = LIBSSH2_FX_OK; /* not an sftp error at all */ + + if(sshc->secondCreateDirs) { + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = sftperr != LIBSSH2_FX_OK ? + sftp_libssh2_error_to_CURLE(sftperr):CURLE_SSH; + failf(data, "Creating the dir/file failed: %s", + sftp_libssh2_strerror(sftperr)); + break; + } + if(((sftperr == LIBSSH2_FX_NO_SUCH_FILE) || + (sftperr == LIBSSH2_FX_FAILURE) || + (sftperr == LIBSSH2_FX_NO_SUCH_PATH)) && + (data->set.ftp_create_missing_dirs && + (strlen(sshp->path) > 1))) { + /* try to create the path remotely */ + rc = 0; /* clear rc and continue */ + sshc->secondCreateDirs = 1; + state(data, SSH_SFTP_CREATE_DIRS_INIT); + break; + } + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = sftperr != LIBSSH2_FX_OK ? + sftp_libssh2_error_to_CURLE(sftperr):CURLE_SSH; + if(!sshc->actualcode) { + /* Sometimes, for some reason libssh2_sftp_last_error() returns zero + even though libssh2_sftp_open() failed previously! We need to + work around that! */ + sshc->actualcode = CURLE_SSH; + sftperr = LIBSSH2_FX_OK; + } + failf(data, "Upload failed: %s (%lu/%d)", + sftperr != LIBSSH2_FX_OK ? + sftp_libssh2_strerror(sftperr):"ssh error", + sftperr, rc); + break; + } + + /* If we have a restart point then we need to seek to the correct + position. */ + if(data->state.resume_from > 0) { + /* Let's read off the proper amount of bytes from the input. */ + if(conn->seek_func) { + Curl_set_in_callback(data, true); + seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, + SEEK_SET); + Curl_set_in_callback(data, false); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; + + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_FTP_COULDNT_USE_REST; + } + /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + do { + size_t readthisamountnow = + (data->state.resume_from - passed > data->set.buffer_size) ? + (size_t)data->set.buffer_size : + curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread; + Curl_set_in_callback(data, true); + actuallyread = data->state.fread_func(data->state.buffer, 1, + readthisamountnow, + data->state.in); + Curl_set_in_callback(data, false); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Failed to read data"); + return CURLE_FTP_COULDNT_USE_REST; + } + } while(passed < data->state.resume_from); + } + + /* now, decrease the size of the read */ + if(data->state.infilesize > 0) { + data->state.infilesize -= data->state.resume_from; + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + + SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); + } + if(data->state.infilesize > 0) { + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + /* upload data */ + Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->sockfd = conn->writesockfd; + + if(result) { + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = result; + } + else { + /* store this original bitmask setup to use later on if we can't + figure out a "real" bitmask */ + sshc->orig_waitfor = data->req.keepon; + + /* we want to use the _sending_ function even when the socket turns + out readable as the underlying libssh2 sftp send function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_OUT; + + /* since we don't really wait for anything at this point, we want the + state machine to move on as soon as possible so we set a very short + timeout here */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + + state(data, SSH_STOP); + } + break; + } + + case SSH_SFTP_CREATE_DIRS_INIT: + if(strlen(sshp->path) > 1) { + sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */ + state(data, SSH_SFTP_CREATE_DIRS); + } + else { + state(data, SSH_SFTP_UPLOAD_INIT); + } + break; + + case SSH_SFTP_CREATE_DIRS: + sshc->slash_pos = strchr(sshc->slash_pos, '/'); + if(sshc->slash_pos) { + *sshc->slash_pos = 0; + + infof(data, "Creating directory '%s'", sshp->path); + state(data, SSH_SFTP_CREATE_DIRS_MKDIR); + break; + } + state(data, SSH_SFTP_UPLOAD_INIT); + break; + + case SSH_SFTP_CREATE_DIRS_MKDIR: + /* 'mode' - parameter is preliminary - default to 0644 */ + rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + data->set.new_directory_perms); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + *sshc->slash_pos = '/'; + ++sshc->slash_pos; + if(rc < 0) { + /* + * Abort if failure wasn't that the dir already exists or the + * permission was denied (creation might succeed further down the + * path) - retry on unspecific FAILURE also + */ + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + if((sftperr != LIBSSH2_FX_FILE_ALREADY_EXISTS) && + (sftperr != LIBSSH2_FX_FAILURE) && + (sftperr != LIBSSH2_FX_PERMISSION_DENIED)) { + result = sftp_libssh2_error_to_CURLE(sftperr); + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = result?result:CURLE_SSH; + break; + } + rc = 0; /* clear rc and continue */ + } + state(data, SSH_SFTP_CREATE_DIRS); + break; + + case SSH_SFTP_READDIR_INIT: + Curl_pgrsSetDownloadSize(data, -1); + if(data->req.no_body) { + state(data, SSH_STOP); + break; + } + + /* + * This is a directory that we are trying to get, so produce a directory + * listing + */ + sshc->sftp_handle = libssh2_sftp_open_ex(sshc->sftp_session, + sshp->path, + curlx_uztoui( + strlen(sshp->path)), + 0, 0, LIBSSH2_SFTP_OPENDIR); + if(!sshc->sftp_handle) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + failf(data, "Could not open directory for reading: %s", + sftp_libssh2_strerror(sftperr)); + state(data, SSH_SFTP_CLOSE); + result = sftp_libssh2_error_to_CURLE(sftperr); + sshc->actualcode = result?result:CURLE_SSH; + break; + } + sshp->readdir_filename = malloc(PATH_MAX + 1); + if(!sshp->readdir_filename) { + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + sshp->readdir_longentry = malloc(PATH_MAX + 1); + if(!sshp->readdir_longentry) { + Curl_safefree(sshp->readdir_filename); + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + Curl_dyn_init(&sshp->readdir, PATH_MAX * 2); + state(data, SSH_SFTP_READDIR); + break; + + case SSH_SFTP_READDIR: + rc = libssh2_sftp_readdir_ex(sshc->sftp_handle, + sshp->readdir_filename, + PATH_MAX, + sshp->readdir_longentry, + PATH_MAX, + &sshp->readdir_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc > 0) { + readdir_len = (size_t) rc; + sshp->readdir_filename[readdir_len] = '\0'; + + if(data->set.list_only) { + result = Curl_client_write(data, CLIENTWRITE_BODY, + sshp->readdir_filename, + readdir_len); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, + (char *)"\n", 1); + if(result) { + state(data, SSH_STOP); + break; + } + + } + else { + result = Curl_dyn_add(&sshp->readdir, sshp->readdir_longentry); + + if(!result) { + if((sshp->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) && + ((sshp->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == + LIBSSH2_SFTP_S_IFLNK)) { + Curl_dyn_init(&sshp->readdir_link, PATH_MAX); + result = Curl_dyn_addf(&sshp->readdir_link, "%s%s", sshp->path, + sshp->readdir_filename); + state(data, SSH_SFTP_READDIR_LINK); + if(!result) + break; + } + else { + state(data, SSH_SFTP_READDIR_BOTTOM); + break; + } + } + sshc->actualcode = result; + state(data, SSH_SFTP_CLOSE); + break; + } + } + else if(rc == 0) { + Curl_safefree(sshp->readdir_filename); + Curl_safefree(sshp->readdir_longentry); + state(data, SSH_SFTP_READDIR_DONE); + break; + } + else if(rc < 0) { + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + result = sftp_libssh2_error_to_CURLE(sftperr); + sshc->actualcode = result?result:CURLE_SSH; + failf(data, "Could not open remote file for reading: %s :: %d", + sftp_libssh2_strerror(sftperr), + libssh2_session_last_errno(sshc->ssh_session)); + Curl_safefree(sshp->readdir_filename); + Curl_safefree(sshp->readdir_longentry); + state(data, SSH_SFTP_CLOSE); + break; + } + break; + + case SSH_SFTP_READDIR_LINK: + rc = + libssh2_sftp_symlink_ex(sshc->sftp_session, + Curl_dyn_ptr(&sshp->readdir_link), + (int)Curl_dyn_len(&sshp->readdir_link), + sshp->readdir_filename, + PATH_MAX, LIBSSH2_SFTP_READLINK); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + Curl_dyn_free(&sshp->readdir_link); + + /* append filename and extra output */ + result = Curl_dyn_addf(&sshp->readdir, " -> %s", sshp->readdir_filename); + + if(result) { + Curl_safefree(sshp->readdir_filename); + Curl_safefree(sshp->readdir_longentry); + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = result; + break; + } + + state(data, SSH_SFTP_READDIR_BOTTOM); + break; + + case SSH_SFTP_READDIR_BOTTOM: + result = Curl_dyn_addn(&sshp->readdir, "\n", 1); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, + Curl_dyn_ptr(&sshp->readdir), + Curl_dyn_len(&sshp->readdir)); + + if(result) { + Curl_dyn_free(&sshp->readdir); + state(data, SSH_STOP); + } + else { + Curl_dyn_reset(&sshp->readdir); + state(data, SSH_SFTP_READDIR); + } + break; + + case SSH_SFTP_READDIR_DONE: + if(libssh2_sftp_closedir(sshc->sftp_handle) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + sshc->sftp_handle = NULL; + Curl_safefree(sshp->readdir_filename); + Curl_safefree(sshp->readdir_longentry); + + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + state(data, SSH_STOP); + break; + + case SSH_SFTP_DOWNLOAD_INIT: + /* + * Work on getting the specified file + */ + sshc->sftp_handle = + libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + LIBSSH2_FXF_READ, data->set.new_file_perms, + LIBSSH2_SFTP_OPENFILE); + if(!sshc->sftp_handle) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + failf(data, "Could not open remote file for reading: %s", + sftp_libssh2_strerror(sftperr)); + state(data, SSH_SFTP_CLOSE); + result = sftp_libssh2_error_to_CURLE(sftperr); + sshc->actualcode = result?result:CURLE_SSH; + break; + } + state(data, SSH_SFTP_DOWNLOAD_STAT); + break; + + case SSH_SFTP_DOWNLOAD_STAT: + { + LIBSSH2_SFTP_ATTRIBUTES attrs; + + rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + LIBSSH2_SFTP_STAT, &attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc || + !(attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) || + (attrs.filesize == 0)) { + /* + * libssh2_sftp_open() didn't return an error, so maybe the server + * just doesn't support stat() + * OR the server doesn't return a file size with a stat() + * OR file size is 0 + */ + data->req.size = -1; + data->req.maxdownload = -1; + Curl_pgrsSetDownloadSize(data, -1); + } + else { + curl_off_t size = attrs.filesize; + + if(size < 0) { + failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(data->state.use_range) { + curl_off_t from, to; + char *ptr; + char *ptr2; + CURLofft to_t; + CURLofft from_t; + + from_t = curlx_strtoofft(data->state.range, &ptr, 10, &from); + if(from_t == CURL_OFFT_FLOW) + return CURLE_RANGE_ERROR; + while(*ptr && (ISBLANK(*ptr) || (*ptr == '-'))) + ptr++; + to_t = curlx_strtoofft(ptr, &ptr2, 10, &to); + if(to_t == CURL_OFFT_FLOW) + return CURLE_RANGE_ERROR; + if((to_t == CURL_OFFT_INVAL) /* no "to" value given */ + || (to >= size)) { + to = size - 1; + } + if(from_t) { + /* from is relative to end of file */ + from = size - to; + to = size - 1; + } + if(from > size) { + failf(data, "Offset (%" + CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" + CURL_FORMAT_CURL_OFF_T ")", from, + (curl_off_t)attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(from > to) { + from = to; + size = 0; + } + else { + size = to - from + 1; + } + + SFTP_SEEK(sshc->sftp_handle, from); + } + data->req.size = size; + data->req.maxdownload = size; + Curl_pgrsSetDownloadSize(data, size); + } + + /* We can resume if we can seek to the resume position */ + if(data->state.resume_from) { + if(data->state.resume_from < 0) { + /* We're supposed to download the last abs(from) bytes */ + if((curl_off_t)attrs.filesize < -data->state.resume_from) { + failf(data, "Offset (%" + CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" + CURL_FORMAT_CURL_OFF_T ")", + data->state.resume_from, (curl_off_t)attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* download from where? */ + data->state.resume_from += attrs.filesize; + } + else { + if((curl_off_t)attrs.filesize < data->state.resume_from) { + failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T + ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", + data->state.resume_from, (curl_off_t)attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + } + /* Now store the number of bytes we are expected to download */ + data->req.size = attrs.filesize - data->state.resume_from; + data->req.maxdownload = attrs.filesize - data->state.resume_from; + Curl_pgrsSetDownloadSize(data, + attrs.filesize - data->state.resume_from); + SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); + } + } + + /* Setup the actual download */ + if(data->req.size == 0) { + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + infof(data, "File already completely downloaded"); + state(data, SSH_STOP); + break; + } + Curl_setup_transfer(data, FIRSTSOCKET, data->req.size, FALSE, -1); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->writesockfd = conn->sockfd; + + /* we want to use the _receiving_ function even when the socket turns + out writableable as the underlying libssh2 recv function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_IN; + + if(result) { + /* this should never occur; the close state should be entered + at the time the error occurs */ + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = result; + } + else { + state(data, SSH_STOP); + } + break; + + case SSH_SFTP_CLOSE: + if(sshc->sftp_handle) { + rc = libssh2_sftp_close(sshc->sftp_handle); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); + } + sshc->sftp_handle = NULL; + } + + Curl_safefree(sshp->path); + + DEBUGF(infof(data, "SFTP DONE done")); + + /* Check if nextstate is set and move .nextstate could be POSTQUOTE_INIT + After nextstate is executed, the control should come back to + SSH_SFTP_CLOSE to pass the correct result back */ + if(sshc->nextstate != SSH_NO_STATE && + sshc->nextstate != SSH_SFTP_CLOSE) { + state(data, sshc->nextstate); + sshc->nextstate = SSH_SFTP_CLOSE; + } + else { + state(data, SSH_STOP); + result = sshc->actualcode; + } + break; + + case SSH_SFTP_SHUTDOWN: + /* during times we get here due to a broken transfer and then the + sftp_handle might not have been taken down so make sure that is done + before we proceed */ + + if(sshc->sftp_handle) { + rc = libssh2_sftp_close(sshc->sftp_handle); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, + NULL, 0); + infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); + } + sshc->sftp_handle = NULL; + } + if(sshc->sftp_session) { + rc = libssh2_sftp_shutdown(sshc->sftp_session); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc < 0) { + infof(data, "Failed to stop libssh2 sftp subsystem"); + } + sshc->sftp_session = NULL; + } + + Curl_safefree(sshc->homedir); + data->state.most_recent_ftp_entrypath = NULL; + + state(data, SSH_SESSION_DISCONNECT); + break; + + case SSH_SCP_TRANS_INIT: + result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); + if(result) { + sshc->actualcode = result; + state(data, SSH_STOP); + break; + } + + if(data->state.upload) { + if(data->state.infilesize < 0) { + failf(data, "SCP requires a known file size for upload"); + sshc->actualcode = CURLE_UPLOAD_FAILED; + state(data, SSH_SCP_CHANNEL_FREE); + break; + } + state(data, SSH_SCP_UPLOAD_INIT); + } + else { + state(data, SSH_SCP_DOWNLOAD_INIT); + } + break; + + case SSH_SCP_UPLOAD_INIT: + /* + * libssh2 requires that the destination path is a full path that + * includes the destination file and name OR ends in a "/" . If this is + * not done the destination file will be named the same name as the last + * directory in the path. + */ + sshc->ssh_channel = + SCP_SEND(sshc->ssh_session, sshp->path, data->set.new_file_perms, + data->state.infilesize); + if(!sshc->ssh_channel) { + int ssh_err; + char *err_msg = NULL; + + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + + ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0)); + failf(data, "%s", err_msg); + state(data, SSH_SCP_CHANNEL_FREE); + sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err); + /* Map generic errors to upload failed */ + if(sshc->actualcode == CURLE_SSH || + sshc->actualcode == CURLE_REMOTE_FILE_NOT_FOUND) + sshc->actualcode = CURLE_UPLOAD_FAILED; + break; + } + + /* upload data */ + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->sockfd = conn->writesockfd; + + if(result) { + state(data, SSH_SCP_CHANNEL_FREE); + sshc->actualcode = result; + } + else { + /* store this original bitmask setup to use later on if we can't + figure out a "real" bitmask */ + sshc->orig_waitfor = data->req.keepon; + + /* we want to use the _sending_ function even when the socket turns + out readable as the underlying libssh2 scp send function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_OUT; + + state(data, SSH_STOP); + } + break; + + case SSH_SCP_DOWNLOAD_INIT: + { + curl_off_t bytecount; + + /* + * We must check the remote file; if it is a directory no values will + * be set in sb + */ + + /* + * If support for >2GB files exists, use it. + */ + + /* get a fresh new channel from the ssh layer */ +#if LIBSSH2_VERSION_NUM < 0x010700 + struct stat sb; + memset(&sb, 0, sizeof(struct stat)); + sshc->ssh_channel = libssh2_scp_recv(sshc->ssh_session, + sshp->path, &sb); +#else + libssh2_struct_stat sb; + memset(&sb, 0, sizeof(libssh2_struct_stat)); + sshc->ssh_channel = libssh2_scp_recv2(sshc->ssh_session, + sshp->path, &sb); +#endif + + if(!sshc->ssh_channel) { + int ssh_err; + char *err_msg = NULL; + + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + + + ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0)); + failf(data, "%s", err_msg); + state(data, SSH_SCP_CHANNEL_FREE); + sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err); + break; + } + + /* download data */ + bytecount = (curl_off_t)sb.st_size; + data->req.maxdownload = (curl_off_t)sb.st_size; + Curl_setup_transfer(data, FIRSTSOCKET, bytecount, FALSE, -1); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->writesockfd = conn->sockfd; + + /* we want to use the _receiving_ function even when the socket turns + out writableable as the underlying libssh2 recv function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_IN; + + if(result) { + state(data, SSH_SCP_CHANNEL_FREE); + sshc->actualcode = result; + } + else + state(data, SSH_STOP); + } + break; + + case SSH_SCP_DONE: + if(data->state.upload) + state(data, SSH_SCP_SEND_EOF); + else + state(data, SSH_SCP_CHANNEL_FREE); + break; + + case SSH_SCP_SEND_EOF: + if(sshc->ssh_channel) { + rc = libssh2_channel_send_eof(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to send libssh2 channel EOF: %d %s", + rc, err_msg); + } + } + state(data, SSH_SCP_WAIT_EOF); + break; + + case SSH_SCP_WAIT_EOF: + if(sshc->ssh_channel) { + rc = libssh2_channel_wait_eof(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to get channel EOF: %d %s", rc, err_msg); + } + } + state(data, SSH_SCP_WAIT_CLOSE); + break; + + case SSH_SCP_WAIT_CLOSE: + if(sshc->ssh_channel) { + rc = libssh2_channel_wait_closed(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Channel failed to close: %d %s", rc, err_msg); + } + } + state(data, SSH_SCP_CHANNEL_FREE); + break; + + case SSH_SCP_CHANNEL_FREE: + if(sshc->ssh_channel) { + rc = libssh2_channel_free(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to free libssh2 scp subsystem: %d %s", + rc, err_msg); + } + sshc->ssh_channel = NULL; + } + DEBUGF(infof(data, "SCP DONE phase complete")); +#if 0 /* PREV */ + state(data, SSH_SESSION_DISCONNECT); +#endif + state(data, SSH_STOP); + result = sshc->actualcode; + break; + + case SSH_SESSION_DISCONNECT: + /* during weird times when we've been prematurely aborted, the channel + is still alive when we reach this state and we MUST kill the channel + properly first */ + if(sshc->ssh_channel) { + rc = libssh2_channel_free(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to free libssh2 scp subsystem: %d %s", + rc, err_msg); + } + sshc->ssh_channel = NULL; + } + + if(sshc->ssh_session) { + rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown"); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to disconnect libssh2 session: %d %s", + rc, err_msg); + } + } + + Curl_safefree(sshc->homedir); + data->state.most_recent_ftp_entrypath = NULL; + + state(data, SSH_SESSION_FREE); + break; + + case SSH_SESSION_FREE: +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + if(sshc->kh) { + libssh2_knownhost_free(sshc->kh); + sshc->kh = NULL; + } +#endif + +#ifdef HAVE_LIBSSH2_AGENT_API + if(sshc->ssh_agent) { + rc = libssh2_agent_disconnect(sshc->ssh_agent); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to disconnect from libssh2 agent: %d %s", + rc, err_msg); + } + libssh2_agent_free(sshc->ssh_agent); + sshc->ssh_agent = NULL; + + /* NB: there is no need to free identities, they are part of internal + agent stuff */ + sshc->sshagent_identity = NULL; + sshc->sshagent_prev_identity = NULL; + } +#endif + + if(sshc->ssh_session) { + rc = libssh2_session_free(sshc->ssh_session); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to free libssh2 session: %d %s", rc, err_msg); + } + sshc->ssh_session = NULL; + } + + /* worst-case scenario cleanup */ + + DEBUGASSERT(sshc->ssh_session == NULL); + DEBUGASSERT(sshc->ssh_channel == NULL); + DEBUGASSERT(sshc->sftp_session == NULL); + DEBUGASSERT(sshc->sftp_handle == NULL); +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + DEBUGASSERT(sshc->kh == NULL); +#endif +#ifdef HAVE_LIBSSH2_AGENT_API + DEBUGASSERT(sshc->ssh_agent == NULL); +#endif + + Curl_safefree(sshc->rsa_pub); + Curl_safefree(sshc->rsa); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + Curl_safefree(sshc->homedir); + + /* the code we are about to return */ + result = sshc->actualcode; + + memset(sshc, 0, sizeof(struct ssh_conn)); + + connclose(conn, "SSH session free"); + sshc->state = SSH_SESSION_FREE; /* current */ + sshc->nextstate = SSH_NO_STATE; + state(data, SSH_STOP); + break; + + case SSH_QUIT: + /* fallthrough, just stop! */ + default: + /* internal error */ + sshc->nextstate = SSH_NO_STATE; + state(data, SSH_STOP); + break; + } + + } while(!rc && (sshc->state != SSH_STOP)); + + if(rc == LIBSSH2_ERROR_EAGAIN) { + /* we would block, we need to wait for the socket to be ready (in the + right direction too)! */ + *block = TRUE; + } + + 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 int ssh_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *sock) +{ + int bitmap = GETSOCK_BLANK; + (void)data; + + sock[0] = conn->sock[FIRSTSOCKET]; + + if(conn->waitfor & KEEP_RECV) + bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); + + if(conn->waitfor & KEEP_SEND) + bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); + + return bitmap; +} + +/* + * 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 + * that the multi interface can take advantage of it. Make sure to call this + * function in all cases so that when it _doesn't_ return EAGAIN we can + * restore the default wait bits. + */ +static void ssh_block2waitfor(struct Curl_easy *data, bool block) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + int dir = 0; + if(block) { + dir = libssh2_session_block_directions(sshc->ssh_session); + if(dir) { + /* translate the libssh2 define bits into our own bit defines */ + conn->waitfor = ((dir&LIBSSH2_SESSION_BLOCK_INBOUND)?KEEP_RECV:0) | + ((dir&LIBSSH2_SESSION_BLOCK_OUTBOUND)?KEEP_SEND:0); + } + } + if(!dir) + /* It didn't block or libssh2 didn't reveal in which direction, put back + the original set */ + conn->waitfor = sshc->orig_waitfor; +} + +/* called repeatedly until done from multi.c */ +static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + CURLcode result = CURLE_OK; + bool block; /* we store the status and use that to provide a ssh_getsock() + implementation */ + do { + result = ssh_statemach_act(data, &block); + *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; + /* if there's no error, it isn't done and it didn't EWOULDBLOCK, then + try again */ + } while(!result && !*done && !block); + ssh_block2waitfor(data, block); + + return result; +} + +static CURLcode ssh_block_statemach(struct Curl_easy *data, + struct connectdata *conn, + bool disconnect) +{ + struct ssh_conn *sshc = &conn->proto.sshc; + CURLcode result = CURLE_OK; + struct curltime dis = Curl_now(); + + while((sshc->state != SSH_STOP) && !result) { + bool block; + timediff_t left = 1000; + struct curltime now = Curl_now(); + + result = ssh_statemach_act(data, &block); + if(result) + break; + + if(!disconnect) { + if(Curl_pgrsUpdate(data)) + return CURLE_ABORTED_BY_CALLBACK; + + result = Curl_speedcheck(data, now); + if(result) + break; + + left = Curl_timeleft(data, NULL, FALSE); + if(left < 0) { + failf(data, "Operation timed out"); + return CURLE_OPERATION_TIMEDOUT; + } + } + else if(Curl_timediff(now, dis) > 1000) { + /* disconnect timeout */ + failf(data, "Disconnect timed out"); + result = CURLE_OK; + break; + } + + if(block) { + int dir = libssh2_session_block_directions(sshc->ssh_session); + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + curl_socket_t fd_read = CURL_SOCKET_BAD; + curl_socket_t fd_write = CURL_SOCKET_BAD; + if(LIBSSH2_SESSION_BLOCK_INBOUND & dir) + fd_read = sock; + if(LIBSSH2_SESSION_BLOCK_OUTBOUND & dir) + fd_write = sock; + /* wait for the socket to become ready */ + (void)Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, + left>1000?1000:left); + } + } + + return result; +} + +/* + * SSH setup and connection + */ +static CURLcode ssh_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + struct SSHPROTO *ssh; + (void)conn; + + data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); + if(!ssh) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +static Curl_recv scp_recv, sftp_recv; +static Curl_send scp_send, sftp_send; + +#ifndef CURL_DISABLE_PROXY +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; + ssize_t nread; + CURLcode result; + struct connectdata *conn = data->conn; + Curl_recv *backup = conn->recv[0]; + struct ssh_conn *ssh = &conn->proto.sshc; + (void)flags; + + /* swap in the TLS reader function for this call only, and then swap back + the SSH one again */ + conn->recv[0] = ssh->tls_recv; + result = Curl_read(data, sock, buffer, length, &nread); + conn->recv[0] = backup; + if(result == CURLE_AGAIN) + return -EAGAIN; /* magic return code for libssh2 */ + else if(result) + return -1; /* generic error */ + Curl_debug(data, CURLINFO_DATA_IN, (char *)buffer, (size_t)nread); + return nread; +} + +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; + ssize_t nwrite; + CURLcode result; + struct connectdata *conn = data->conn; + Curl_send *backup = conn->send[0]; + struct ssh_conn *ssh = &conn->proto.sshc; + (void)flags; + + /* swap in the TLS writer function for this call only, and then swap back + the SSH one again */ + conn->send[0] = ssh->tls_send; + result = Curl_write(data, sock, buffer, length, &nwrite); + conn->send[0] = backup; + if(result == CURLE_AGAIN) + return -EAGAIN; /* magic return code for libssh2 */ + else if(result) + return -1; /* error */ + Curl_debug(data, CURLINFO_DATA_OUT, (char *)buffer, (size_t)nwrite); + return nwrite; +} +#endif + +/* + * Curl_ssh_connect() gets called from Curl_protocol_connect() to allow us to + * do protocol-specific actions at connect-time. + */ +static CURLcode ssh_connect(struct Curl_easy *data, bool *done) +{ +#ifdef CURL_LIBSSH2_DEBUG + curl_socket_t sock; +#endif + struct ssh_conn *sshc; + CURLcode result; + struct connectdata *conn = data->conn; + + /* initialize per-handle data if not already */ + if(!data->req.p.ssh) { + result = ssh_setup_connection(data, conn); + if(result) + return result; + } + + /* We default to persistent connections. We set this already in this connect + function to make the reuse checks properly be able to check this bit. */ + connkeep(conn, "SSH default"); + + sshc = &conn->proto.sshc; + +#ifdef CURL_LIBSSH2_DEBUG + if(conn->user) { + infof(data, "User: %s", conn->user); + } + if(conn->passwd) { + infof(data, "Password: %s", conn->passwd); + } + sock = conn->sock[FIRSTSOCKET]; +#endif /* CURL_LIBSSH2_DEBUG */ + + /* libcurl MUST to set custom memory functions so that the kbd_callback + funciton's memory allocations can be properled freed */ + sshc->ssh_session = libssh2_session_init_ex(my_libssh2_malloc, + my_libssh2_free, + my_libssh2_realloc, data); + + if(!sshc->ssh_session) { + failf(data, "Failure initialising ssh session"); + return CURLE_FAILED_INIT; + } + +#ifdef HAVE_LIBSSH2_VERSION + /* Set the packet read timeout if the libssh2 version supports it */ +#if LIBSSH2_VERSION_NUM >= 0x010B00 + if(data->set.server_response_timeout > 0) { + libssh2_session_set_read_timeout(sshc->ssh_session, + data->set.server_response_timeout / 1000); + } +#endif +#endif + +#ifndef CURL_DISABLE_PROXY + if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) { + /* + * This crazy union dance is here to avoid assigning a void pointer a + * function pointer as it is invalid C. The problem is of course that + * libssh2 has such an API... + */ + union receive { + void *recvp; + ssize_t (*recvptr)(libssh2_socket_t, void *, size_t, int, void **); + }; + union transfer { + void *sendp; + ssize_t (*sendptr)(libssh2_socket_t, const void *, size_t, int, void **); + }; + union receive sshrecv; + union transfer sshsend; + + sshrecv.recvptr = ssh_tls_recv; + sshsend.sendptr = ssh_tls_send; + + infof(data, "Uses HTTPS proxy"); + /* + 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 + sendcb(libssh2_socket_t sock, const void *buffer, size_t length, + int flags, void **abstract); + + */ + libssh2_session_callback_set(sshc->ssh_session, + LIBSSH2_CALLBACK_RECV, sshrecv.recvp); + libssh2_session_callback_set(sshc->ssh_session, + LIBSSH2_CALLBACK_SEND, sshsend.sendp); + + /* Store the underlying TLS recv/send function pointers to be used when + reading from the proxy */ + sshc->tls_recv = conn->recv[FIRSTSOCKET]; + sshc->tls_send = conn->send[FIRSTSOCKET]; + } + +#endif /* CURL_DISABLE_PROXY */ + if(conn->handler->protocol & CURLPROTO_SCP) { + conn->recv[FIRSTSOCKET] = scp_recv; + conn->send[FIRSTSOCKET] = scp_send; + } + else { + conn->recv[FIRSTSOCKET] = sftp_recv; + conn->send[FIRSTSOCKET] = sftp_send; + } + + if(data->set.ssh_compression) { +#if LIBSSH2_VERSION_NUM >= 0x010208 + if(libssh2_session_flag(sshc->ssh_session, LIBSSH2_FLAG_COMPRESS, 1) < 0) +#endif + infof(data, "Failed to enable compression for ssh session"); + } + +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + if(data->set.str[STRING_SSH_KNOWNHOSTS]) { + int rc; + sshc->kh = libssh2_knownhost_init(sshc->ssh_session); + if(!sshc->kh) { + libssh2_session_free(sshc->ssh_session); + sshc->ssh_session = NULL; + return CURLE_FAILED_INIT; + } + + /* read all known hosts from there */ + rc = libssh2_knownhost_readfile(sshc->kh, + data->set.str[STRING_SSH_KNOWNHOSTS], + LIBSSH2_KNOWNHOST_FILE_OPENSSH); + if(rc < 0) + infof(data, "Failed to read known hosts from %s", + data->set.str[STRING_SSH_KNOWNHOSTS]); + } +#endif /* HAVE_LIBSSH2_KNOWNHOST_API */ + +#ifdef CURL_LIBSSH2_DEBUG + libssh2_trace(sshc->ssh_session, ~0); + infof(data, "SSH socket: %d", (int)sock); +#endif /* CURL_LIBSSH2_DEBUG */ + + state(data, SSH_INIT); + + result = ssh_multi_statemach(data, done); + + return result; +} + +/* + *********************************************************************** + * + * scp_perform() + * + * This is the actual DO function for SCP. Get a file according to + * the options previously setup. + */ + +static +CURLcode scp_perform(struct Curl_easy *data, + bool *connected, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + + DEBUGF(infof(data, "DO phase starts")); + + *dophase_done = FALSE; /* not done yet */ + + /* start the first command in the DO phase */ + state(data, SSH_SCP_TRANS_INIT); + + /* run the state-machine */ + result = ssh_multi_statemach(data, dophase_done); + + *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); + + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete")); + } + + return result; +} + +/* called from multi.c while DOing */ +static CURLcode scp_doing(struct Curl_easy *data, + bool *dophase_done) +{ + CURLcode result; + result = ssh_multi_statemach(data, dophase_done); + + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete")); + } + return result; +} + +/* + * The DO function is generic for both protocols. There was previously two + * separate ones but this way means less duplicated code. + */ + +static CURLcode ssh_do(struct Curl_easy *data, bool *done) +{ + CURLcode result; + bool connected = 0; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + + *done = FALSE; /* default to false */ + + data->req.size = -1; /* make sure this is unknown at this point */ + + sshc->actualcode = CURLE_OK; /* reset error code */ + sshc->secondCreateDirs = 0; /* reset the create dir attempt state + variable */ + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, -1); + Curl_pgrsSetDownloadSize(data, -1); + + if(conn->handler->protocol & CURLPROTO_SCP) + result = scp_perform(data, &connected, done); + else + result = sftp_perform(data, &connected, done); + + return result; +} + +/* BLOCKING, but the function is using the state machine so the only reason + this is still blocking is that the multi interface code has no support for + disconnecting operations that takes a while */ +static CURLcode scp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + CURLcode result = CURLE_OK; + struct ssh_conn *sshc = &conn->proto.sshc; + (void) dead_connection; + + if(sshc->ssh_session) { + /* only if there's a session still around to use! */ + state(data, SSH_SESSION_DISCONNECT); + result = ssh_block_statemach(data, conn, TRUE); + } + + return result; +} + +/* generic done function for both SCP and SFTP called from their specific + done functions */ +static CURLcode ssh_done(struct Curl_easy *data, CURLcode status) +{ + CURLcode result = CURLE_OK; + struct SSHPROTO *sshp = data->req.p.ssh; + struct connectdata *conn = data->conn; + + if(!status) + /* run the state-machine */ + result = ssh_block_statemach(data, conn, FALSE); + else + result = status; + + Curl_safefree(sshp->path); + Curl_safefree(sshp->readdir_filename); + Curl_safefree(sshp->readdir_longentry); + Curl_dyn_free(&sshp->readdir); + + if(Curl_pgrsDone(data)) + return CURLE_ABORTED_BY_CALLBACK; + + data->req.keepon = 0; /* clear all bits */ + return result; +} + + +static CURLcode scp_done(struct Curl_easy *data, CURLcode status, + bool premature) +{ + (void)premature; /* not used */ + + if(!status) + state(data, SSH_SCP_DONE); + + return ssh_done(data, status); + +} + +static ssize_t scp_send(struct Curl_easy *data, int sockindex, + const void *mem, size_t len, CURLcode *err) +{ + ssize_t nwrite; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + (void)sockindex; /* we only support SCP on the fixed known primary socket */ + + /* libssh2_channel_write() returns int! */ + nwrite = (ssize_t) libssh2_channel_write(sshc->ssh_channel, mem, len); + + ssh_block2waitfor(data, (nwrite == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + + if(nwrite == LIBSSH2_ERROR_EAGAIN) { + *err = CURLE_AGAIN; + nwrite = 0; + } + else if(nwrite < LIBSSH2_ERROR_NONE) { + *err = libssh2_session_error_to_CURLE((int)nwrite); + nwrite = -1; + } + + return nwrite; +} + +static ssize_t scp_recv(struct Curl_easy *data, int sockindex, + char *mem, size_t len, CURLcode *err) +{ + ssize_t nread; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + (void)sockindex; /* we only support SCP on the fixed known primary socket */ + + /* libssh2_channel_read() returns int */ + nread = (ssize_t) libssh2_channel_read(sshc->ssh_channel, mem, len); + + ssh_block2waitfor(data, (nread == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + if(nread == LIBSSH2_ERROR_EAGAIN) { + *err = CURLE_AGAIN; + nread = -1; + } + + return nread; +} + +/* + * =============== SFTP =============== + */ + +/* + *********************************************************************** + * + * sftp_perform() + * + * This is the actual DO function for SFTP. Get a file/directory according to + * the options previously setup. + */ + +static +CURLcode sftp_perform(struct Curl_easy *data, + bool *connected, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + + DEBUGF(infof(data, "DO phase starts")); + + *dophase_done = FALSE; /* not done yet */ + + /* start the first command in the DO phase */ + state(data, SSH_SFTP_QUOTE_INIT); + + /* run the state-machine */ + result = ssh_multi_statemach(data, dophase_done); + + *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); + + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete")); + } + + return result; +} + +/* called from multi.c while DOing */ +static CURLcode sftp_doing(struct Curl_easy *data, + bool *dophase_done) +{ + CURLcode result = ssh_multi_statemach(data, dophase_done); + + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete")); + } + return result; +} + +/* BLOCKING, but the function is using the state machine so the only reason + this is still blocking is that the multi interface code has no support for + disconnecting operations that takes a while */ +static CURLcode sftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection) +{ + CURLcode result = CURLE_OK; + struct ssh_conn *sshc = &conn->proto.sshc; + (void) dead_connection; + + DEBUGF(infof(data, "SSH DISCONNECT starts now")); + + if(sshc->ssh_session) { + /* only if there's a session still around to use! */ + state(data, SSH_SFTP_SHUTDOWN); + result = ssh_block_statemach(data, conn, TRUE); + } + + DEBUGF(infof(data, "SSH DISCONNECT is done")); + + return result; + +} + +static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, + bool premature) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + + if(!status) { + /* Post quote commands are executed after the SFTP_CLOSE state to avoid + errors that could happen due to open file handles during POSTQUOTE + operation */ + if(!premature && data->set.postquote && !conn->bits.retry) + sshc->nextstate = SSH_SFTP_POSTQUOTE_INIT; + state(data, SSH_SFTP_CLOSE); + } + return ssh_done(data, status); +} + +/* return number of sent bytes */ +static ssize_t sftp_send(struct Curl_easy *data, int sockindex, + const void *mem, size_t len, CURLcode *err) +{ + ssize_t nwrite; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + (void)sockindex; + + nwrite = libssh2_sftp_write(sshc->sftp_handle, mem, len); + + ssh_block2waitfor(data, (nwrite == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + + if(nwrite == LIBSSH2_ERROR_EAGAIN) { + *err = CURLE_AGAIN; + nwrite = 0; + } + else if(nwrite < LIBSSH2_ERROR_NONE) { + *err = libssh2_session_error_to_CURLE((int)nwrite); + nwrite = -1; + } + + return nwrite; +} + +/* + * Return number of received (decrypted) bytes + * or <0 on error + */ +static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, + char *mem, size_t len, CURLcode *err) +{ + ssize_t nread; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + (void)sockindex; + + nread = libssh2_sftp_read(sshc->sftp_handle, mem, len); + + ssh_block2waitfor(data, (nread == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + + if(nread == LIBSSH2_ERROR_EAGAIN) { + *err = CURLE_AGAIN; + nread = -1; + + } + else if(nread < 0) { + *err = libssh2_session_error_to_CURLE((int)nread); + } + return nread; +} + +static const char *sftp_libssh2_strerror(unsigned long err) +{ + switch(err) { + case LIBSSH2_FX_NO_SUCH_FILE: + return "No such file or directory"; + + case LIBSSH2_FX_PERMISSION_DENIED: + return "Permission denied"; + + case LIBSSH2_FX_FAILURE: + return "Operation failed"; + + case LIBSSH2_FX_BAD_MESSAGE: + return "Bad message from SFTP server"; + + case LIBSSH2_FX_NO_CONNECTION: + return "Not connected to SFTP server"; + + case LIBSSH2_FX_CONNECTION_LOST: + return "Connection to SFTP server lost"; + + case LIBSSH2_FX_OP_UNSUPPORTED: + return "Operation not supported by SFTP server"; + + case LIBSSH2_FX_INVALID_HANDLE: + return "Invalid handle"; + + case LIBSSH2_FX_NO_SUCH_PATH: + return "No such file or directory"; + + case LIBSSH2_FX_FILE_ALREADY_EXISTS: + return "File already exists"; + + case LIBSSH2_FX_WRITE_PROTECT: + return "File is write protected"; + + case LIBSSH2_FX_NO_MEDIA: + return "No media"; + + case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: + return "Disk full"; + + case LIBSSH2_FX_QUOTA_EXCEEDED: + return "User quota exceeded"; + + case LIBSSH2_FX_UNKNOWN_PRINCIPLE: + return "Unknown principle"; + + case LIBSSH2_FX_LOCK_CONFlICT: + return "File lock conflict"; + + case LIBSSH2_FX_DIR_NOT_EMPTY: + return "Directory not empty"; + + case LIBSSH2_FX_NOT_A_DIRECTORY: + return "Not a directory"; + + case LIBSSH2_FX_INVALID_FILENAME: + return "Invalid filename"; + + case LIBSSH2_FX_LINK_LOOP: + return "Link points to itself"; + } + return "Unknown error in libssh2"; +} + +CURLcode Curl_ssh_init(void) +{ +#ifdef HAVE_LIBSSH2_INIT + if(libssh2_init(0)) { + DEBUGF(fprintf(stderr, "Error: libssh2_init failed\n")); + return CURLE_FAILED_INIT; + } +#endif + return CURLE_OK; +} + +void Curl_ssh_cleanup(void) +{ +#ifdef HAVE_LIBSSH2_EXIT + (void)libssh2_exit(); +#endif +} + +void Curl_ssh_version(char *buffer, size_t buflen) +{ + (void)msnprintf(buffer, buflen, "libssh2/%s", CURL_LIBSSH2_VERSION); +} + +/* The SSH session is associated with the *CONNECTION* but the callback user + * pointer is an easy handle pointer. This function allows us to reassign the + * user pointer to the *CURRENT* (new) easy handle. + */ +static void ssh_attach(struct Curl_easy *data, struct connectdata *conn) +{ + DEBUGASSERT(data); + DEBUGASSERT(conn); + if(conn->handler->protocol & PROTO_FAMILY_SSH) { + struct ssh_conn *sshc = &conn->proto.sshc; + if(sshc->ssh_session) { + /* only re-attach if the session already exists */ + void **abstract = libssh2_session_abstract(sshc->ssh_session); + *abstract = data; + } + } +} +#endif /* USE_LIBSSH2 */ diff --git a/Utilities/cmcurl/lib/vssh/ssh.h b/Utilities/cmcurl/lib/vssh/ssh.h new file mode 100644 index 0000000..ca0533a --- /dev/null +++ b/Utilities/cmcurl/lib/vssh/ssh.h @@ -0,0 +1,273 @@ +#ifndef HEADER_CURL_SSH_H +#define HEADER_CURL_SSH_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_LIBSSH2) +#include <libssh2.h> +#include <libssh2_sftp.h> +#elif defined(USE_LIBSSH) +#include <libssh/libssh.h> +#include <libssh/sftp.h> +#elif defined(USE_WOLFSSH) +#include <wolfssh/ssh.h> +#include <wolfssh/wolfsftp.h> +#endif + +/**************************************************************************** + * SSH unique setup + ***************************************************************************/ +typedef enum { + SSH_NO_STATE = -1, /* Used for "nextState" so say there is none */ + SSH_STOP = 0, /* do nothing state, stops the state machine */ + + SSH_INIT, /* First state in SSH-CONNECT */ + SSH_S_STARTUP, /* Session startup */ + SSH_HOSTKEY, /* verify hostkey */ + SSH_AUTHLIST, + SSH_AUTH_PKEY_INIT, + SSH_AUTH_PKEY, + SSH_AUTH_PASS_INIT, + SSH_AUTH_PASS, + SSH_AUTH_AGENT_INIT, /* initialize then wait for connection to agent */ + SSH_AUTH_AGENT_LIST, /* ask for list then wait for entire list to come */ + SSH_AUTH_AGENT, /* attempt one key at a time */ + SSH_AUTH_HOST_INIT, + SSH_AUTH_HOST, + SSH_AUTH_KEY_INIT, + SSH_AUTH_KEY, + SSH_AUTH_GSSAPI, + SSH_AUTH_DONE, + SSH_SFTP_INIT, + SSH_SFTP_REALPATH, /* Last state in SSH-CONNECT */ + + SSH_SFTP_QUOTE_INIT, /* First state in SFTP-DO */ + SSH_SFTP_POSTQUOTE_INIT, /* (Possibly) First state in SFTP-DONE */ + SSH_SFTP_QUOTE, + SSH_SFTP_NEXT_QUOTE, + SSH_SFTP_QUOTE_STAT, + SSH_SFTP_QUOTE_SETSTAT, + SSH_SFTP_QUOTE_SYMLINK, + SSH_SFTP_QUOTE_MKDIR, + SSH_SFTP_QUOTE_RENAME, + SSH_SFTP_QUOTE_RMDIR, + SSH_SFTP_QUOTE_UNLINK, + SSH_SFTP_QUOTE_STATVFS, + SSH_SFTP_GETINFO, + SSH_SFTP_FILETIME, + SSH_SFTP_TRANS_INIT, + SSH_SFTP_UPLOAD_INIT, + SSH_SFTP_CREATE_DIRS_INIT, + SSH_SFTP_CREATE_DIRS, + SSH_SFTP_CREATE_DIRS_MKDIR, + SSH_SFTP_READDIR_INIT, + SSH_SFTP_READDIR, + SSH_SFTP_READDIR_LINK, + SSH_SFTP_READDIR_BOTTOM, + SSH_SFTP_READDIR_DONE, + SSH_SFTP_DOWNLOAD_INIT, + SSH_SFTP_DOWNLOAD_STAT, /* Last state in SFTP-DO */ + SSH_SFTP_CLOSE, /* Last state in SFTP-DONE */ + SSH_SFTP_SHUTDOWN, /* First state in SFTP-DISCONNECT */ + SSH_SCP_TRANS_INIT, /* First state in SCP-DO */ + SSH_SCP_UPLOAD_INIT, + SSH_SCP_DOWNLOAD_INIT, + SSH_SCP_DOWNLOAD, + SSH_SCP_DONE, + SSH_SCP_SEND_EOF, + SSH_SCP_WAIT_EOF, + SSH_SCP_WAIT_CLOSE, + SSH_SCP_CHANNEL_FREE, /* Last state in SCP-DONE */ + SSH_SESSION_DISCONNECT, /* First state in SCP-DISCONNECT */ + SSH_SESSION_FREE, /* Last state in SCP/SFTP-DISCONNECT */ + SSH_QUIT, + SSH_LAST /* never used */ +} sshstate; + +/* this struct is used in the HandleData struct which is part of the + Curl_easy, which means this is used on a per-easy handle basis. + Everything that is strictly related to a connection is banned from this + struct. */ +struct SSHPROTO { + char *path; /* the path we operate on */ +#ifdef USE_LIBSSH2 + struct dynbuf readdir_link; + struct dynbuf readdir; + char *readdir_filename; + char *readdir_longentry; + + LIBSSH2_SFTP_ATTRIBUTES quote_attrs; /* used by the SFTP_QUOTE state */ + + /* Here's a set of struct members used by the SFTP_READDIR state */ + LIBSSH2_SFTP_ATTRIBUTES readdir_attrs; +#endif +}; + +/* ssh_conn is used for struct connection-oriented data in the connectdata + struct */ +struct ssh_conn { + const char *authlist; /* List of auth. methods, managed by libssh2 */ + + /* common */ + const char *passphrase; /* pass-phrase to use */ + char *rsa_pub; /* strdup'ed public key file */ + char *rsa; /* strdup'ed private key file */ + bool authed; /* the connection has been authenticated fine */ + bool acceptfail; /* used by the SFTP_QUOTE (continue if + quote command fails) */ + sshstate state; /* always use ssh.c:state() to change state! */ + sshstate nextstate; /* the state to goto after stopping */ + CURLcode actualcode; /* the actual error code */ + struct curl_slist *quote_item; /* for the quote option */ + char *quote_path1; /* two generic pointers for the QUOTE stuff */ + char *quote_path2; + + char *homedir; /* when doing SFTP we figure out home dir in the + connect phase */ + /* end of READDIR stuff */ + + int secondCreateDirs; /* counter use by the code to see if the + second attempt has been made to change + to/create a directory */ + int orig_waitfor; /* default READ/WRITE bits wait for */ + char *slash_pos; /* used by the SFTP_CREATE_DIRS state */ + +#if defined(USE_LIBSSH) + char *readdir_linkPath; + size_t readdir_len; + struct dynbuf readdir_buf; +/* our variables */ + unsigned kbd_state; /* 0 or 1 */ + ssh_key privkey; + ssh_key pubkey; + int auth_methods; + ssh_session ssh_session; + ssh_scp scp_session; + sftp_session sftp_session; + sftp_file sftp_file; + sftp_dir sftp_dir; + + unsigned sftp_recv_state; /* 0 or 1 */ + int sftp_file_index; /* for async read */ + sftp_attributes readdir_attrs; /* used by the SFTP readdir actions */ + sftp_attributes readdir_link_attrs; /* used by the SFTP readdir actions */ + sftp_attributes quote_attrs; /* used by the SFTP_QUOTE state */ + + const char *readdir_filename; /* points within readdir_attrs */ + const char *readdir_longentry; + char *readdir_tmp; +#elif defined(USE_LIBSSH2) + LIBSSH2_SESSION *ssh_session; /* Secure Shell session */ + LIBSSH2_CHANNEL *ssh_channel; /* Secure Shell channel handle */ + LIBSSH2_SFTP *sftp_session; /* SFTP handle */ + LIBSSH2_SFTP_HANDLE *sftp_handle; + +#ifndef CURL_DISABLE_PROXY + /* for HTTPS proxy storage */ + Curl_recv *tls_recv; + Curl_send *tls_send; +#endif + +#ifdef HAVE_LIBSSH2_AGENT_API + LIBSSH2_AGENT *ssh_agent; /* proxy to ssh-agent/pageant */ + struct libssh2_agent_publickey *sshagent_identity, + *sshagent_prev_identity; +#endif + + /* note that HAVE_LIBSSH2_KNOWNHOST_API is a define set in the libssh2.h + header */ +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + LIBSSH2_KNOWNHOSTS *kh; +#endif +#elif defined(USE_WOLFSSH) + WOLFSSH *ssh_session; + WOLFSSH_CTX *ctx; + word32 handleSz; + byte handle[WOLFSSH_MAX_HANDLE]; + curl_off_t offset; +#endif /* USE_LIBSSH */ +}; + +#if defined(USE_LIBSSH2) + +/* Feature detection based on version numbers to better work with + non-configure platforms */ + +#if !defined(LIBSSH2_VERSION_NUM) || (LIBSSH2_VERSION_NUM < 0x001000) +# error "SCP/SFTP protocols require libssh2 0.16 or later" +#endif + +#if LIBSSH2_VERSION_NUM >= 0x010000 +#define HAVE_LIBSSH2_SFTP_SEEK64 1 +#endif + +#if LIBSSH2_VERSION_NUM >= 0x010100 +#define HAVE_LIBSSH2_VERSION 1 +#endif + +#if LIBSSH2_VERSION_NUM >= 0x010205 +#define HAVE_LIBSSH2_INIT 1 +#define HAVE_LIBSSH2_EXIT 1 +#endif + +#if LIBSSH2_VERSION_NUM >= 0x010206 +#define HAVE_LIBSSH2_KNOWNHOST_CHECKP 1 +#define HAVE_LIBSSH2_SCP_SEND64 1 +#endif + +#if LIBSSH2_VERSION_NUM >= 0x010208 +#define HAVE_LIBSSH2_SESSION_HANDSHAKE 1 +#endif + +#ifdef HAVE_LIBSSH2_VERSION +/* get it run-time if possible */ +#define CURL_LIBSSH2_VERSION libssh2_version(0) +#else +/* use build-time if run-time not possible */ +#define CURL_LIBSSH2_VERSION LIBSSH2_VERSION +#endif + +#endif /* USE_LIBSSH2 */ + +#ifdef USE_SSH + +extern const struct Curl_handler Curl_handler_scp; +extern const struct Curl_handler Curl_handler_sftp; + +/* generic SSH backend functions */ +CURLcode Curl_ssh_init(void); +void Curl_ssh_cleanup(void); +void Curl_ssh_version(char *buffer, size_t buflen); +void Curl_ssh_attach(struct Curl_easy *data, + struct connectdata *conn); +#else +/* for non-SSH builds */ +#define Curl_ssh_cleanup() +#define Curl_ssh_attach(x,y) +#define Curl_ssh_init() 0 +#endif + +#endif /* HEADER_CURL_SSH_H */ diff --git a/Utilities/cmcurl/lib/vssh/wolfssh.c b/Utilities/cmcurl/lib/vssh/wolfssh.c new file mode 100644 index 0000000..4da7e9d --- /dev/null +++ b/Utilities/cmcurl/lib/vssh/wolfssh.c @@ -0,0 +1,1171 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_WOLFSSH + +#include <limits.h> + +#include <wolfssh/ssh.h> +#include <wolfssh/wolfsftp.h> +#include "urldata.h" +#include "cfilters.h" +#include "connect.h" +#include "sendf.h" +#include "progress.h" +#include "curl_path.h" +#include "strtoofft.h" +#include "transfer.h" +#include "speedcheck.h" +#include "select.h" +#include "multiif.h" +#include "warnless.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +static CURLcode wssh_connect(struct Curl_easy *data, bool *done); +static CURLcode wssh_multi_statemach(struct Curl_easy *data, bool *done); +static CURLcode wssh_do(struct Curl_easy *data, bool *done); +#if 0 +static CURLcode wscp_done(struct Curl_easy *data, + CURLcode, bool premature); +static CURLcode wscp_doing(struct Curl_easy *data, + bool *dophase_done); +static CURLcode wscp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection); +#endif +static CURLcode wsftp_done(struct Curl_easy *data, + CURLcode, bool premature); +static CURLcode wsftp_doing(struct Curl_easy *data, + bool *dophase_done); +static CURLcode wsftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead); +static int wssh_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *sock); +static CURLcode wssh_setup_connection(struct Curl_easy *data, + struct connectdata *conn); + +#if 0 +/* + * SCP protocol handler. + */ + +const struct Curl_handler Curl_handler_scp = { + "SCP", /* scheme */ + wssh_setup_connection, /* setup_connection */ + wssh_do, /* do_it */ + wscp_done, /* done */ + ZERO_NULL, /* do_more */ + wssh_connect, /* connect_it */ + wssh_multi_statemach, /* connecting */ + wscp_doing, /* doing */ + wssh_getsock, /* proto_getsock */ + wssh_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + wssh_getsock, /* perform_getsock */ + wscp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_SSH, /* defport */ + CURLPROTO_SCP, /* protocol */ + PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION + | PROTOPT_NOURLQUERY /* flags */ +}; + +#endif + +/* + * SFTP protocol handler. + */ + +const struct Curl_handler Curl_handler_sftp = { + "SFTP", /* scheme */ + wssh_setup_connection, /* setup_connection */ + wssh_do, /* do_it */ + wsftp_done, /* done */ + ZERO_NULL, /* do_more */ + wssh_connect, /* connect_it */ + wssh_multi_statemach, /* connecting */ + wsftp_doing, /* doing */ + wssh_getsock, /* proto_getsock */ + wssh_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + wssh_getsock, /* perform_getsock */ + wsftp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_SSH, /* defport */ + CURLPROTO_SFTP, /* protocol */ + CURLPROTO_SFTP, /* family */ + PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION + | PROTOPT_NOURLQUERY /* flags */ +}; + +/* + * SSH State machine related code + */ +/* This is the ONLY way to change SSH state! */ +static void state(struct Curl_easy *data, sshstate nowstate) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[] = { + "SSH_STOP", + "SSH_INIT", + "SSH_S_STARTUP", + "SSH_HOSTKEY", + "SSH_AUTHLIST", + "SSH_AUTH_PKEY_INIT", + "SSH_AUTH_PKEY", + "SSH_AUTH_PASS_INIT", + "SSH_AUTH_PASS", + "SSH_AUTH_AGENT_INIT", + "SSH_AUTH_AGENT_LIST", + "SSH_AUTH_AGENT", + "SSH_AUTH_HOST_INIT", + "SSH_AUTH_HOST", + "SSH_AUTH_KEY_INIT", + "SSH_AUTH_KEY", + "SSH_AUTH_GSSAPI", + "SSH_AUTH_DONE", + "SSH_SFTP_INIT", + "SSH_SFTP_REALPATH", + "SSH_SFTP_QUOTE_INIT", + "SSH_SFTP_POSTQUOTE_INIT", + "SSH_SFTP_QUOTE", + "SSH_SFTP_NEXT_QUOTE", + "SSH_SFTP_QUOTE_STAT", + "SSH_SFTP_QUOTE_SETSTAT", + "SSH_SFTP_QUOTE_SYMLINK", + "SSH_SFTP_QUOTE_MKDIR", + "SSH_SFTP_QUOTE_RENAME", + "SSH_SFTP_QUOTE_RMDIR", + "SSH_SFTP_QUOTE_UNLINK", + "SSH_SFTP_QUOTE_STATVFS", + "SSH_SFTP_GETINFO", + "SSH_SFTP_FILETIME", + "SSH_SFTP_TRANS_INIT", + "SSH_SFTP_UPLOAD_INIT", + "SSH_SFTP_CREATE_DIRS_INIT", + "SSH_SFTP_CREATE_DIRS", + "SSH_SFTP_CREATE_DIRS_MKDIR", + "SSH_SFTP_READDIR_INIT", + "SSH_SFTP_READDIR", + "SSH_SFTP_READDIR_LINK", + "SSH_SFTP_READDIR_BOTTOM", + "SSH_SFTP_READDIR_DONE", + "SSH_SFTP_DOWNLOAD_INIT", + "SSH_SFTP_DOWNLOAD_STAT", + "SSH_SFTP_CLOSE", + "SSH_SFTP_SHUTDOWN", + "SSH_SCP_TRANS_INIT", + "SSH_SCP_UPLOAD_INIT", + "SSH_SCP_DOWNLOAD_INIT", + "SSH_SCP_DOWNLOAD", + "SSH_SCP_DONE", + "SSH_SCP_SEND_EOF", + "SSH_SCP_WAIT_EOF", + "SSH_SCP_WAIT_CLOSE", + "SSH_SCP_CHANNEL_FREE", + "SSH_SESSION_DISCONNECT", + "SSH_SESSION_FREE", + "QUIT" + }; + + /* a precaution to make sure the lists are in sync */ + DEBUGASSERT(sizeof(names)/sizeof(names[0]) == SSH_LAST); + + if(sshc->state != nowstate) { + infof(data, "wolfssh %p state change from %s to %s", + (void *)sshc, names[sshc->state], names[nowstate]); + } +#endif + + sshc->state = nowstate; +} + +static ssize_t wscp_send(struct Curl_easy *data, int sockindex, + const void *mem, size_t len, CURLcode *err) +{ + ssize_t nwrite = 0; + (void)data; + (void)sockindex; /* we only support SCP on the fixed known primary socket */ + (void)mem; + (void)len; + (void)err; + + return nwrite; +} + +static ssize_t wscp_recv(struct Curl_easy *data, int sockindex, + char *mem, size_t len, CURLcode *err) +{ + ssize_t nread = 0; + (void)data; + (void)sockindex; /* we only support SCP on the fixed known primary socket */ + (void)mem; + (void)len; + (void)err; + + return nread; +} + +/* return number of sent bytes */ +static ssize_t wsftp_send(struct Curl_easy *data, int sockindex, + const void *mem, size_t len, CURLcode *err) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + word32 offset[2]; + int rc; + (void)sockindex; + + offset[0] = (word32)sshc->offset&0xFFFFFFFF; + offset[1] = (word32)(sshc->offset>>32)&0xFFFFFFFF; + + rc = wolfSSH_SFTP_SendWritePacket(sshc->ssh_session, sshc->handle, + sshc->handleSz, + &offset[0], + (byte *)mem, (word32)len); + + if(rc == WS_FATAL_ERROR) + rc = wolfSSH_get_error(sshc->ssh_session); + if(rc == WS_WANT_READ) { + conn->waitfor = KEEP_RECV; + *err = CURLE_AGAIN; + return -1; + } + else if(rc == WS_WANT_WRITE) { + conn->waitfor = KEEP_SEND; + *err = CURLE_AGAIN; + return -1; + } + if(rc < 0) { + failf(data, "wolfSSH_SFTP_SendWritePacket returned %d", rc); + return -1; + } + DEBUGASSERT(rc == (int)len); + infof(data, "sent %zu bytes SFTP from offset %" CURL_FORMAT_CURL_OFF_T, + len, sshc->offset); + sshc->offset += len; + return (ssize_t)rc; +} + +/* + * Return number of received (decrypted) bytes + * or <0 on error + */ +static ssize_t wsftp_recv(struct Curl_easy *data, int sockindex, + char *mem, size_t len, CURLcode *err) +{ + int rc; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + word32 offset[2]; + (void)sockindex; + + offset[0] = (word32)sshc->offset&0xFFFFFFFF; + offset[1] = (word32)(sshc->offset>>32)&0xFFFFFFFF; + + rc = wolfSSH_SFTP_SendReadPacket(sshc->ssh_session, sshc->handle, + sshc->handleSz, + &offset[0], + (byte *)mem, (word32)len); + if(rc == WS_FATAL_ERROR) + rc = wolfSSH_get_error(sshc->ssh_session); + if(rc == WS_WANT_READ) { + conn->waitfor = KEEP_RECV; + *err = CURLE_AGAIN; + return -1; + } + else if(rc == WS_WANT_WRITE) { + conn->waitfor = KEEP_SEND; + *err = CURLE_AGAIN; + return -1; + } + + DEBUGASSERT(rc <= (int)len); + + if(rc < 0) { + failf(data, "wolfSSH_SFTP_SendReadPacket returned %d", rc); + return -1; + } + sshc->offset += len; + + return (ssize_t)rc; +} + +/* + * SSH setup and connection + */ +static CURLcode wssh_setup_connection(struct Curl_easy *data, + struct connectdata *conn) +{ + struct SSHPROTO *ssh; + (void)conn; + + data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); + if(!ssh) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +static int userauth(byte authtype, + WS_UserAuthData* authdata, + void *ctx) +{ + struct Curl_easy *data = ctx; + DEBUGF(infof(data, "wolfssh callback: type %s", + authtype == WOLFSSH_USERAUTH_PASSWORD ? "PASSWORD" : + "PUBLICCKEY")); + if(authtype == WOLFSSH_USERAUTH_PASSWORD) { + authdata->sf.password.password = (byte *)data->conn->passwd; + authdata->sf.password.passwordSz = (word32) strlen(data->conn->passwd); + } + + return 0; +} + +static CURLcode wssh_connect(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int rc; + + /* initialize per-handle data if not already */ + if(!data->req.p.ssh) + wssh_setup_connection(data, conn); + + /* We default to persistent connections. We set this already in this connect + function to make the reuse checks properly be able to check this bit. */ + connkeep(conn, "SSH default"); + + if(conn->handler->protocol & CURLPROTO_SCP) { + conn->recv[FIRSTSOCKET] = wscp_recv; + conn->send[FIRSTSOCKET] = wscp_send; + } + else { + conn->recv[FIRSTSOCKET] = wsftp_recv; + conn->send[FIRSTSOCKET] = wsftp_send; + } + sshc = &conn->proto.sshc; + sshc->ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if(!sshc->ctx) { + failf(data, "No wolfSSH context"); + goto error; + } + + sshc->ssh_session = wolfSSH_new(sshc->ctx); + if(!sshc->ssh_session) { + failf(data, "No wolfSSH session"); + goto error; + } + + rc = wolfSSH_SetUsername(sshc->ssh_session, conn->user); + if(rc != WS_SUCCESS) { + failf(data, "wolfSSH failed to set user name"); + goto error; + } + + /* set callback for authentication */ + wolfSSH_SetUserAuth(sshc->ctx, userauth); + wolfSSH_SetUserAuthCtx(sshc->ssh_session, data); + + rc = wolfSSH_set_fd(sshc->ssh_session, (int)sock); + if(rc) { + failf(data, "wolfSSH failed to set socket"); + goto error; + } + +#if 0 + wolfSSH_Debugging_ON(); +#endif + + *done = TRUE; + if(conn->handler->protocol & CURLPROTO_SCP) + state(data, SSH_INIT); + else + state(data, SSH_SFTP_INIT); + + return wssh_multi_statemach(data, done); +error: + wolfSSH_free(sshc->ssh_session); + wolfSSH_CTX_free(sshc->ctx); + return CURLE_FAILED_INIT; +} + +/* + * wssh_statemach_act() runs the SSH state machine as far as it can without + * blocking and without reaching the end. The data the pointer 'block' points + * to will be set to TRUE if the wolfssh function returns EAGAIN meaning it + * wants to be called again when the socket is ready + */ + +static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + struct SSHPROTO *sftp_scp = data->req.p.ssh; + WS_SFTPNAME *name; + int rc = 0; + *block = FALSE; /* we're not blocking by default */ + + do { + switch(sshc->state) { + case SSH_INIT: + state(data, SSH_S_STARTUP); + break; + + case SSH_S_STARTUP: + rc = wolfSSH_connect(sshc->ssh_session); + if(rc != WS_SUCCESS) + rc = wolfSSH_get_error(sshc->ssh_session); + if(rc == WS_WANT_READ) { + *block = TRUE; + conn->waitfor = KEEP_RECV; + return CURLE_OK; + } + else if(rc == WS_WANT_WRITE) { + *block = TRUE; + conn->waitfor = KEEP_SEND; + return CURLE_OK; + } + else if(rc != WS_SUCCESS) { + state(data, SSH_STOP); + return CURLE_SSH; + } + infof(data, "wolfssh connected"); + state(data, SSH_STOP); + break; + case SSH_STOP: + break; + + case SSH_SFTP_INIT: + rc = wolfSSH_SFTP_connect(sshc->ssh_session); + if(rc != WS_SUCCESS) + rc = wolfSSH_get_error(sshc->ssh_session); + if(rc == WS_WANT_READ) { + *block = TRUE; + conn->waitfor = KEEP_RECV; + return CURLE_OK; + } + else if(rc == WS_WANT_WRITE) { + *block = TRUE; + conn->waitfor = KEEP_SEND; + return CURLE_OK; + } + else if(rc == WS_SUCCESS) { + infof(data, "wolfssh SFTP connected"); + state(data, SSH_SFTP_REALPATH); + } + else { + failf(data, "wolfssh SFTP connect error %d", rc); + return CURLE_SSH; + } + break; + case SSH_SFTP_REALPATH: + name = wolfSSH_SFTP_RealPath(sshc->ssh_session, (char *)"."); + rc = wolfSSH_get_error(sshc->ssh_session); + if(rc == WS_WANT_READ) { + *block = TRUE; + conn->waitfor = KEEP_RECV; + return CURLE_OK; + } + else if(rc == WS_WANT_WRITE) { + *block = TRUE; + conn->waitfor = KEEP_SEND; + return CURLE_OK; + } + else if(name && (rc == WS_SUCCESS)) { + sshc->homedir = malloc(name->fSz + 1); + if(!sshc->homedir) { + sshc->actualcode = CURLE_OUT_OF_MEMORY; + } + else { + memcpy(sshc->homedir, name->fName, name->fSz); + sshc->homedir[name->fSz] = 0; + infof(data, "wolfssh SFTP realpath succeeded"); + } + wolfSSH_SFTPNAME_list_free(name); + state(data, SSH_STOP); + return CURLE_OK; + } + failf(data, "wolfssh SFTP realpath %d", rc); + return CURLE_SSH; + + case SSH_SFTP_QUOTE_INIT: + result = Curl_getworkingpath(data, sshc->homedir, &sftp_scp->path); + if(result) { + sshc->actualcode = result; + state(data, SSH_STOP); + break; + } + + if(data->set.quote) { + infof(data, "Sending quote commands"); + sshc->quote_item = data->set.quote; + state(data, SSH_SFTP_QUOTE); + } + else { + state(data, SSH_SFTP_GETINFO); + } + break; + case SSH_SFTP_GETINFO: + if(data->set.get_filetime) { + state(data, SSH_SFTP_FILETIME); + } + else { + state(data, SSH_SFTP_TRANS_INIT); + } + break; + case SSH_SFTP_TRANS_INIT: + if(data->state.upload) + state(data, SSH_SFTP_UPLOAD_INIT); + else { + if(sftp_scp->path[strlen(sftp_scp->path)-1] == '/') + state(data, SSH_SFTP_READDIR_INIT); + else + state(data, SSH_SFTP_DOWNLOAD_INIT); + } + break; + case SSH_SFTP_UPLOAD_INIT: { + word32 flags; + WS_SFTP_FILEATRB createattrs; + if(data->state.resume_from) { + WS_SFTP_FILEATRB attrs; + if(data->state.resume_from < 0) { + rc = wolfSSH_SFTP_STAT(sshc->ssh_session, sftp_scp->path, + &attrs); + if(rc != WS_SUCCESS) + break; + + if(rc) { + data->state.resume_from = 0; + } + else { + curl_off_t size = ((curl_off_t)attrs.sz[1] << 32) | attrs.sz[0]; + if(size < 0) { + failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + data->state.resume_from = size; + } + } + } + + if(data->set.remote_append) + /* Try to open for append, but create if nonexisting */ + flags = WOLFSSH_FXF_WRITE|WOLFSSH_FXF_CREAT|WOLFSSH_FXF_APPEND; + else if(data->state.resume_from > 0) + /* If we have restart position then open for append */ + flags = WOLFSSH_FXF_WRITE|WOLFSSH_FXF_APPEND; + else + /* Clear file before writing (normal behavior) */ + flags = WOLFSSH_FXF_WRITE|WOLFSSH_FXF_CREAT|WOLFSSH_FXF_TRUNC; + + memset(&createattrs, 0, sizeof(createattrs)); + createattrs.per = (word32)data->set.new_file_perms; + sshc->handleSz = sizeof(sshc->handle); + rc = wolfSSH_SFTP_Open(sshc->ssh_session, sftp_scp->path, + flags, &createattrs, + sshc->handle, &sshc->handleSz); + if(rc == WS_FATAL_ERROR) + rc = wolfSSH_get_error(sshc->ssh_session); + if(rc == WS_WANT_READ) { + *block = TRUE; + conn->waitfor = KEEP_RECV; + return CURLE_OK; + } + else if(rc == WS_WANT_WRITE) { + *block = TRUE; + conn->waitfor = KEEP_SEND; + return CURLE_OK; + } + else if(rc == WS_SUCCESS) { + infof(data, "wolfssh SFTP open succeeded"); + } + else { + failf(data, "wolfssh SFTP upload open failed: %d", rc); + return CURLE_SSH; + } + state(data, SSH_SFTP_DOWNLOAD_STAT); + + /* If we have a restart point then we need to seek to the correct + position. */ + if(data->state.resume_from > 0) { + /* Let's read off the proper amount of bytes from the input. */ + int seekerr = CURL_SEEKFUNC_OK; + if(conn->seek_func) { + Curl_set_in_callback(data, true); + seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, + SEEK_SET); + Curl_set_in_callback(data, false); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; + + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_FTP_COULDNT_USE_REST; + } + /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + do { + size_t readthisamountnow = + (data->state.resume_from - passed > data->set.buffer_size) ? + (size_t)data->set.buffer_size : + curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread; + Curl_set_in_callback(data, true); + actuallyread = data->state.fread_func(data->state.buffer, 1, + readthisamountnow, + data->state.in); + Curl_set_in_callback(data, false); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Failed to read data"); + return CURLE_FTP_COULDNT_USE_REST; + } + } while(passed < data->state.resume_from); + } + + /* now, decrease the size of the read */ + if(data->state.infilesize > 0) { + data->state.infilesize -= data->state.resume_from; + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + + sshc->offset += data->state.resume_from; + } + if(data->state.infilesize > 0) { + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + /* upload data */ + Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->sockfd = conn->writesockfd; + + if(result) { + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = result; + } + else { + /* store this original bitmask setup to use later on if we can't + figure out a "real" bitmask */ + sshc->orig_waitfor = data->req.keepon; + + /* we want to use the _sending_ function even when the socket turns + out readable as the underlying libssh2 sftp send function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_OUT; + + /* since we don't really wait for anything at this point, we want the + state machine to move on as soon as possible so we set a very short + timeout here */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + + state(data, SSH_STOP); + } + break; + } + case SSH_SFTP_DOWNLOAD_INIT: + sshc->handleSz = sizeof(sshc->handle); + rc = wolfSSH_SFTP_Open(sshc->ssh_session, sftp_scp->path, + WOLFSSH_FXF_READ, NULL, + sshc->handle, &sshc->handleSz); + if(rc == WS_FATAL_ERROR) + rc = wolfSSH_get_error(sshc->ssh_session); + if(rc == WS_WANT_READ) { + *block = TRUE; + conn->waitfor = KEEP_RECV; + return CURLE_OK; + } + else if(rc == WS_WANT_WRITE) { + *block = TRUE; + conn->waitfor = KEEP_SEND; + return CURLE_OK; + } + else if(rc == WS_SUCCESS) { + infof(data, "wolfssh SFTP open succeeded"); + state(data, SSH_SFTP_DOWNLOAD_STAT); + return CURLE_OK; + } + + failf(data, "wolfssh SFTP open failed: %d", rc); + return CURLE_SSH; + + case SSH_SFTP_DOWNLOAD_STAT: { + WS_SFTP_FILEATRB attrs; + curl_off_t size; + + rc = wolfSSH_SFTP_STAT(sshc->ssh_session, sftp_scp->path, &attrs); + if(rc == WS_FATAL_ERROR) + rc = wolfSSH_get_error(sshc->ssh_session); + if(rc == WS_WANT_READ) { + *block = TRUE; + conn->waitfor = KEEP_RECV; + return CURLE_OK; + } + else if(rc == WS_WANT_WRITE) { + *block = TRUE; + conn->waitfor = KEEP_SEND; + return CURLE_OK; + } + else if(rc == WS_SUCCESS) { + infof(data, "wolfssh STAT succeeded"); + } + else { + failf(data, "wolfssh SFTP open failed: %d", rc); + data->req.size = -1; + data->req.maxdownload = -1; + Curl_pgrsSetDownloadSize(data, -1); + return CURLE_SSH; + } + + size = ((curl_off_t)attrs.sz[1] <<32) | attrs.sz[0]; + + data->req.size = size; + data->req.maxdownload = size; + Curl_pgrsSetDownloadSize(data, size); + + infof(data, "SFTP download %" CURL_FORMAT_CURL_OFF_T " bytes", size); + + /* We cannot seek with wolfSSH so resuming and range requests are not + possible */ + if(data->state.use_range || data->state.resume_from) { + infof(data, "wolfSSH cannot do range/seek on SFTP"); + return CURLE_BAD_DOWNLOAD_RESUME; + } + + /* Setup the actual download */ + if(data->req.size == 0) { + /* no data to transfer */ + Curl_setup_transfer(data, -1, -1, FALSE, -1); + infof(data, "File already completely downloaded"); + state(data, SSH_STOP); + break; + } + Curl_setup_transfer(data, FIRSTSOCKET, data->req.size, FALSE, -1); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->writesockfd = conn->sockfd; + + /* we want to use the _receiving_ function even when the socket turns + out writableable as the underlying libssh2 recv function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_IN; + + if(result) { + /* this should never occur; the close state should be entered + at the time the error occurs */ + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = result; + } + else { + state(data, SSH_STOP); + } + break; + } + case SSH_SFTP_CLOSE: + if(sshc->handleSz) + rc = wolfSSH_SFTP_Close(sshc->ssh_session, sshc->handle, + sshc->handleSz); + else + rc = WS_SUCCESS; /* directory listing */ + if(rc == WS_WANT_READ) { + *block = TRUE; + conn->waitfor = KEEP_RECV; + return CURLE_OK; + } + else if(rc == WS_WANT_WRITE) { + *block = TRUE; + conn->waitfor = KEEP_SEND; + return CURLE_OK; + } + else if(rc == WS_SUCCESS) { + state(data, SSH_STOP); + return CURLE_OK; + } + + failf(data, "wolfssh SFTP CLOSE failed: %d", rc); + return CURLE_SSH; + + case SSH_SFTP_READDIR_INIT: + Curl_pgrsSetDownloadSize(data, -1); + if(data->req.no_body) { + state(data, SSH_STOP); + break; + } + state(data, SSH_SFTP_READDIR); + break; + + case SSH_SFTP_READDIR: + name = wolfSSH_SFTP_LS(sshc->ssh_session, sftp_scp->path); + if(!name) + rc = wolfSSH_get_error(sshc->ssh_session); + else + rc = WS_SUCCESS; + + if(rc == WS_WANT_READ) { + *block = TRUE; + conn->waitfor = KEEP_RECV; + return CURLE_OK; + } + else if(rc == WS_WANT_WRITE) { + *block = TRUE; + conn->waitfor = KEEP_SEND; + return CURLE_OK; + } + else if(name && (rc == WS_SUCCESS)) { + WS_SFTPNAME *origname = name; + result = CURLE_OK; + while(name) { + char *line = aprintf("%s\n", + data->set.list_only ? + name->fName : name->lName); + if(!line) { + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + result = Curl_client_write(data, CLIENTWRITE_BODY, + line, strlen(line)); + free(line); + if(result) { + sshc->actualcode = result; + break; + } + name = name->next; + } + wolfSSH_SFTPNAME_list_free(origname); + state(data, SSH_STOP); + return result; + } + failf(data, "wolfssh SFTP ls failed: %d", rc); + return CURLE_SSH; + + case SSH_SFTP_SHUTDOWN: + Curl_safefree(sshc->homedir); + wolfSSH_free(sshc->ssh_session); + wolfSSH_CTX_free(sshc->ctx); + state(data, SSH_STOP); + return CURLE_OK; + default: + break; + } + } while(!rc && (sshc->state != SSH_STOP)); + return result; +} + +/* called repeatedly until done from multi.c */ +static CURLcode wssh_multi_statemach(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + CURLcode result = CURLE_OK; + bool block; /* we store the status and use that to provide a ssh_getsock() + implementation */ + do { + result = wssh_statemach_act(data, &block); + *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; + /* if there's no error, it isn't done and it didn't EWOULDBLOCK, then + try again */ + if(*done) { + DEBUGF(infof(data, "wssh_statemach_act says DONE")); + } + } while(!result && !*done && !block); + + return result; +} + +static +CURLcode wscp_perform(struct Curl_easy *data, + bool *connected, + bool *dophase_done) +{ + (void)data; + (void)connected; + (void)dophase_done; + return CURLE_OK; +} + +static +CURLcode wsftp_perform(struct Curl_easy *data, + bool *connected, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + + DEBUGF(infof(data, "DO phase starts")); + + *dophase_done = FALSE; /* not done yet */ + + /* start the first command in the DO phase */ + state(data, SSH_SFTP_QUOTE_INIT); + + /* run the state-machine */ + result = wssh_multi_statemach(data, dophase_done); + + *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); + + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete")); + } + + return result; +} + +/* + * The DO function is generic for both protocols. + */ +static CURLcode wssh_do(struct Curl_easy *data, bool *done) +{ + CURLcode result; + bool connected = 0; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + + *done = FALSE; /* default to false */ + data->req.size = -1; /* make sure this is unknown at this point */ + sshc->actualcode = CURLE_OK; /* reset error code */ + sshc->secondCreateDirs = 0; /* reset the create dir attempt state + variable */ + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, -1); + Curl_pgrsSetDownloadSize(data, -1); + + if(conn->handler->protocol & CURLPROTO_SCP) + result = wscp_perform(data, &connected, done); + else + result = wsftp_perform(data, &connected, done); + + return result; +} + +static CURLcode wssh_block_statemach(struct Curl_easy *data, + bool disconnect) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + CURLcode result = CURLE_OK; + + while((sshc->state != SSH_STOP) && !result) { + bool block; + timediff_t left = 1000; + struct curltime now = Curl_now(); + + result = wssh_statemach_act(data, &block); + if(result) + break; + + if(!disconnect) { + if(Curl_pgrsUpdate(data)) + return CURLE_ABORTED_BY_CALLBACK; + + result = Curl_speedcheck(data, now); + if(result) + break; + + left = Curl_timeleft(data, NULL, FALSE); + if(left < 0) { + failf(data, "Operation timed out"); + return CURLE_OPERATION_TIMEDOUT; + } + } + + if(!result) { + int dir = conn->waitfor; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + curl_socket_t fd_read = CURL_SOCKET_BAD; + curl_socket_t fd_write = CURL_SOCKET_BAD; + if(dir == KEEP_RECV) + fd_read = sock; + else if(dir == KEEP_SEND) + fd_write = sock; + + /* wait for the socket to become ready */ + (void)Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, + left>1000?1000:left); /* ignore result */ + } + } + + return result; +} + +/* generic done function for both SCP and SFTP called from their specific + done functions */ +static CURLcode wssh_done(struct Curl_easy *data, CURLcode status) +{ + CURLcode result = CURLE_OK; + struct SSHPROTO *sftp_scp = data->req.p.ssh; + + if(!status) { + /* run the state-machine */ + result = wssh_block_statemach(data, FALSE); + } + else + result = status; + + if(sftp_scp) + Curl_safefree(sftp_scp->path); + if(Curl_pgrsDone(data)) + return CURLE_ABORTED_BY_CALLBACK; + + data->req.keepon = 0; /* clear all bits */ + return result; +} + +#if 0 +static CURLcode wscp_done(struct Curl_easy *data, + CURLcode code, bool premature) +{ + CURLcode result = CURLE_OK; + (void)conn; + (void)code; + (void)premature; + + return result; +} + +static CURLcode wscp_doing(struct Curl_easy *data, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + (void)conn; + (void)dophase_done; + + return result; +} + +static CURLcode wscp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection) +{ + CURLcode result = CURLE_OK; + (void)data; + (void)conn; + (void)dead_connection; + + return result; +} +#endif + +static CURLcode wsftp_done(struct Curl_easy *data, + CURLcode code, bool premature) +{ + (void)premature; + state(data, SSH_SFTP_CLOSE); + + return wssh_done(data, code); +} + +static CURLcode wsftp_doing(struct Curl_easy *data, + bool *dophase_done) +{ + CURLcode result = wssh_multi_statemach(data, dophase_done); + + if(*dophase_done) { + DEBUGF(infof(data, "DO phase is complete")); + } + return result; +} + +static CURLcode wsftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead) +{ + CURLcode result = CURLE_OK; + (void)dead; + + DEBUGF(infof(data, "SSH DISCONNECT starts now")); + + if(conn->proto.sshc.ssh_session) { + /* only if there's a session still around to use! */ + state(data, SSH_SFTP_SHUTDOWN); + result = wssh_block_statemach(data, TRUE); + } + + DEBUGF(infof(data, "SSH DISCONNECT is done")); + return result; +} + +static int wssh_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *sock) +{ + int bitmap = GETSOCK_BLANK; + int dir = conn->waitfor; + (void)data; + sock[0] = conn->sock[FIRSTSOCKET]; + + if(dir == KEEP_RECV) + bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); + else if(dir == KEEP_SEND) + bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); + + return bitmap; +} + +void Curl_ssh_version(char *buffer, size_t buflen) +{ + (void)msnprintf(buffer, buflen, "wolfssh/%s", LIBWOLFSSH_VERSION_STRING); +} + +CURLcode Curl_ssh_init(void) +{ + if(WS_SUCCESS != wolfSSH_Init()) { + DEBUGF(fprintf(stderr, "Error: wolfSSH_Init failed\n")); + return CURLE_FAILED_INIT; + } + + return CURLE_OK; +} +void Curl_ssh_cleanup(void) +{ + (void)wolfSSH_Cleanup(); +} + +#endif /* USE_WOLFSSH */ diff --git a/Utilities/cmcurl/lib/vtls/bearssl.c b/Utilities/cmcurl/lib/vtls/bearssl.c new file mode 100644 index 0000000..a6566f4 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/bearssl.c @@ -0,0 +1,1220 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Michael Forney, <mforney@mforney.org> + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_BEARSSL + +#include <bearssl.h> + +#include "bearssl.h" +#include "urldata.h" +#include "sendf.h" +#include "inet_pton.h" +#include "vtls.h" +#include "vtls_int.h" +#include "connect.h" +#include "select.h" +#include "multiif.h" +#include "curl_printf.h" +#include "strcase.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +struct x509_context { + const br_x509_class *vtable; + br_x509_minimal_context minimal; + br_x509_decoder_context decoder; + bool verifyhost; + bool verifypeer; + int cert_num; +}; + +struct bearssl_ssl_backend_data { + br_ssl_client_context ctx; + struct x509_context x509; + unsigned char buf[BR_SSL_BUFSIZE_BIDI]; + br_x509_trust_anchor *anchors; + size_t anchors_len; + const char *protocols[ALPN_ENTRIES_MAX]; + /* SSL client context is active */ + bool active; + /* size of pending write, yet to be flushed */ + size_t pending_write; +}; + +struct cafile_parser { + CURLcode err; + bool in_cert; + br_x509_decoder_context xc; + /* array of trust anchors loaded from CAfile */ + br_x509_trust_anchor *anchors; + size_t anchors_len; + /* buffer for DN data */ + unsigned char dn[1024]; + size_t dn_len; +}; + +#define CAFILE_SOURCE_PATH 1 +#define CAFILE_SOURCE_BLOB 2 +struct cafile_source { + int type; + const char *data; + size_t len; +}; + +static void append_dn(void *ctx, const void *buf, size_t len) +{ + struct cafile_parser *ca = ctx; + + if(ca->err != CURLE_OK || !ca->in_cert) + return; + if(sizeof(ca->dn) - ca->dn_len < len) { + ca->err = CURLE_FAILED_INIT; + return; + } + memcpy(ca->dn + ca->dn_len, buf, len); + ca->dn_len += len; +} + +static void x509_push(void *ctx, const void *buf, size_t len) +{ + struct cafile_parser *ca = ctx; + + if(ca->in_cert) + br_x509_decoder_push(&ca->xc, buf, len); +} + +static CURLcode load_cafile(struct cafile_source *source, + br_x509_trust_anchor **anchors, + size_t *anchors_len) +{ + struct cafile_parser ca; + br_pem_decoder_context pc; + br_x509_trust_anchor *ta; + size_t ta_size; + br_x509_trust_anchor *new_anchors; + size_t new_anchors_len; + br_x509_pkey *pkey; + FILE *fp = 0; + unsigned char buf[BUFSIZ]; + const unsigned char *p; + const char *name; + size_t n, i, pushed; + + DEBUGASSERT(source->type == CAFILE_SOURCE_PATH + || source->type == CAFILE_SOURCE_BLOB); + + if(source->type == CAFILE_SOURCE_PATH) { + fp = fopen(source->data, "rb"); + if(!fp) + return CURLE_SSL_CACERT_BADFILE; + } + + if(source->type == CAFILE_SOURCE_BLOB && source->len > (size_t)INT_MAX) + return CURLE_SSL_CACERT_BADFILE; + + ca.err = CURLE_OK; + ca.in_cert = FALSE; + ca.anchors = NULL; + ca.anchors_len = 0; + br_pem_decoder_init(&pc); + br_pem_decoder_setdest(&pc, x509_push, &ca); + do { + if(source->type == CAFILE_SOURCE_PATH) { + n = fread(buf, 1, sizeof(buf), fp); + if(n == 0) + break; + p = buf; + } + else if(source->type == CAFILE_SOURCE_BLOB) { + n = source->len; + p = (unsigned char *) source->data; + } + while(n) { + pushed = br_pem_decoder_push(&pc, p, n); + if(ca.err) + goto fail; + p += pushed; + n -= pushed; + + switch(br_pem_decoder_event(&pc)) { + case 0: + break; + case BR_PEM_BEGIN_OBJ: + name = br_pem_decoder_name(&pc); + if(strcmp(name, "CERTIFICATE") && strcmp(name, "X509 CERTIFICATE")) + break; + br_x509_decoder_init(&ca.xc, append_dn, &ca); + ca.in_cert = TRUE; + ca.dn_len = 0; + break; + case BR_PEM_END_OBJ: + if(!ca.in_cert) + break; + ca.in_cert = FALSE; + if(br_x509_decoder_last_error(&ca.xc)) { + ca.err = CURLE_SSL_CACERT_BADFILE; + goto fail; + } + /* add trust anchor */ + if(ca.anchors_len == SIZE_MAX / sizeof(ca.anchors[0])) { + ca.err = CURLE_OUT_OF_MEMORY; + goto fail; + } + new_anchors_len = ca.anchors_len + 1; + new_anchors = realloc(ca.anchors, + new_anchors_len * sizeof(ca.anchors[0])); + if(!new_anchors) { + ca.err = CURLE_OUT_OF_MEMORY; + goto fail; + } + ca.anchors = new_anchors; + ca.anchors_len = new_anchors_len; + ta = &ca.anchors[ca.anchors_len - 1]; + ta->dn.data = NULL; + ta->flags = 0; + if(br_x509_decoder_isCA(&ca.xc)) + ta->flags |= BR_X509_TA_CA; + pkey = br_x509_decoder_get_pkey(&ca.xc); + if(!pkey) { + ca.err = CURLE_SSL_CACERT_BADFILE; + goto fail; + } + ta->pkey = *pkey; + + /* calculate space needed for trust anchor data */ + ta_size = ca.dn_len; + switch(pkey->key_type) { + case BR_KEYTYPE_RSA: + ta_size += pkey->key.rsa.nlen + pkey->key.rsa.elen; + break; + case BR_KEYTYPE_EC: + ta_size += pkey->key.ec.qlen; + break; + default: + ca.err = CURLE_FAILED_INIT; + goto fail; + } + + /* fill in trust anchor DN and public key data */ + ta->dn.data = malloc(ta_size); + if(!ta->dn.data) { + ca.err = CURLE_OUT_OF_MEMORY; + goto fail; + } + memcpy(ta->dn.data, ca.dn, ca.dn_len); + ta->dn.len = ca.dn_len; + switch(pkey->key_type) { + case BR_KEYTYPE_RSA: + ta->pkey.key.rsa.n = ta->dn.data + ta->dn.len; + memcpy(ta->pkey.key.rsa.n, pkey->key.rsa.n, pkey->key.rsa.nlen); + ta->pkey.key.rsa.e = ta->pkey.key.rsa.n + ta->pkey.key.rsa.nlen; + memcpy(ta->pkey.key.rsa.e, pkey->key.rsa.e, pkey->key.rsa.elen); + break; + case BR_KEYTYPE_EC: + ta->pkey.key.ec.q = ta->dn.data + ta->dn.len; + memcpy(ta->pkey.key.ec.q, pkey->key.ec.q, pkey->key.ec.qlen); + break; + } + break; + default: + ca.err = CURLE_SSL_CACERT_BADFILE; + goto fail; + } + } + } while(source->type != CAFILE_SOURCE_BLOB); + if(fp && ferror(fp)) + ca.err = CURLE_READ_ERROR; + else if(ca.in_cert) + ca.err = CURLE_SSL_CACERT_BADFILE; + +fail: + if(fp) + fclose(fp); + if(ca.err == CURLE_OK) { + *anchors = ca.anchors; + *anchors_len = ca.anchors_len; + } + else { + for(i = 0; i < ca.anchors_len; ++i) + free(ca.anchors[i].dn.data); + free(ca.anchors); + } + + return ca.err; +} + +static void x509_start_chain(const br_x509_class **ctx, + const char *server_name) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + + if(!x509->verifypeer) { + x509->cert_num = 0; + return; + } + + if(!x509->verifyhost) + server_name = NULL; + x509->minimal.vtable->start_chain(&x509->minimal.vtable, server_name); +} + +static void x509_start_cert(const br_x509_class **ctx, uint32_t length) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + + if(!x509->verifypeer) { + /* Only decode the first cert in the chain to obtain the public key */ + if(x509->cert_num == 0) + br_x509_decoder_init(&x509->decoder, NULL, NULL); + return; + } + + x509->minimal.vtable->start_cert(&x509->minimal.vtable, length); +} + +static void x509_append(const br_x509_class **ctx, const unsigned char *buf, + size_t len) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + + if(!x509->verifypeer) { + if(x509->cert_num == 0) + br_x509_decoder_push(&x509->decoder, buf, len); + return; + } + + x509->minimal.vtable->append(&x509->minimal.vtable, buf, len); +} + +static void x509_end_cert(const br_x509_class **ctx) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + + if(!x509->verifypeer) { + x509->cert_num++; + return; + } + + x509->minimal.vtable->end_cert(&x509->minimal.vtable); +} + +static unsigned x509_end_chain(const br_x509_class **ctx) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + + if(!x509->verifypeer) { + return br_x509_decoder_last_error(&x509->decoder); + } + + return x509->minimal.vtable->end_chain(&x509->minimal.vtable); +} + +static const br_x509_pkey *x509_get_pkey(const br_x509_class *const *ctx, + unsigned *usages) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + + if(!x509->verifypeer) { + /* Nothing in the chain is verified, just return the public key of the + first certificate and allow its usage for both TLS_RSA_* and + TLS_ECDHE_* */ + if(usages) + *usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN; + return br_x509_decoder_get_pkey(&x509->decoder); + } + + return x509->minimal.vtable->get_pkey(&x509->minimal.vtable, usages); +} + +static const br_x509_class x509_vtable = { + sizeof(struct x509_context), + x509_start_chain, + x509_start_cert, + x509_append, + x509_end_cert, + x509_end_chain, + x509_get_pkey +}; + +struct st_cipher { + const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */ + const char *alias_name; /* Alias name is the same as OpenSSL cipher name */ + uint16_t num; /* BearSSL cipher suite */ +}; + +/* Macro to initialize st_cipher data structure */ +#define CIPHER_DEF(num, alias) { #num, alias, BR_##num } + +static const struct st_cipher ciphertable[] = { + /* RFC 2246 TLS 1.0 */ + CIPHER_DEF(TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ + "DES-CBC3-SHA"), + + /* RFC 3268 TLS 1.0 AES */ + CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ + "AES128-SHA"), + CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ + "AES256-SHA"), + + /* RFC 5246 TLS 1.2 */ + CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ + "AES128-SHA256"), + CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ + "AES256-SHA256"), + + /* RFC 5288 TLS 1.2 AES GCM */ + CIPHER_DEF(TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ + "AES128-GCM-SHA256"), + CIPHER_DEF(TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ + "AES256-GCM-SHA384"), + + /* RFC 4492 TLS 1.0 ECC */ + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ + "ECDH-ECDSA-DES-CBC3-SHA"), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ + "ECDH-ECDSA-AES128-SHA"), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ + "ECDH-ECDSA-AES256-SHA"), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ + "ECDHE-ECDSA-DES-CBC3-SHA"), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ + "ECDHE-ECDSA-AES128-SHA"), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ + "ECDHE-ECDSA-AES256-SHA"), + CIPHER_DEF(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ + "ECDH-RSA-DES-CBC3-SHA"), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ + "ECDH-RSA-AES128-SHA"), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ + "ECDH-RSA-AES256-SHA"), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ + "ECDHE-RSA-DES-CBC3-SHA"), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ + "ECDHE-RSA-AES128-SHA"), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ + "ECDHE-RSA-AES256-SHA"), + + /* RFC 5289 TLS 1.2 ECC HMAC SHA256/384 */ + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ + "ECDHE-ECDSA-AES128-SHA256"), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ + "ECDHE-ECDSA-AES256-SHA384"), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ + "ECDH-ECDSA-AES128-SHA256"), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ + "ECDH-ECDSA-AES256-SHA384"), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ + "ECDHE-RSA-AES128-SHA256"), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ + "ECDHE-RSA-AES256-SHA384"), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ + "ECDH-RSA-AES128-SHA256"), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ + "ECDH-RSA-AES256-SHA384"), + + /* RFC 5289 TLS 1.2 GCM */ + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ + "ECDHE-ECDSA-AES128-GCM-SHA256"), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ + "ECDHE-ECDSA-AES256-GCM-SHA384"), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ + "ECDH-ECDSA-AES128-GCM-SHA256"), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ + "ECDH-ECDSA-AES256-GCM-SHA384"), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ + "ECDHE-RSA-AES128-GCM-SHA256"), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ + "ECDHE-RSA-AES256-GCM-SHA384"), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ + "ECDH-RSA-AES128-GCM-SHA256"), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ + "ECDH-RSA-AES256-GCM-SHA384"), +#ifdef BR_TLS_RSA_WITH_AES_128_CCM + + /* RFC 6655 TLS 1.2 CCM + Supported since BearSSL 0.6 */ + CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM, /* 0xC09C */ + "AES128-CCM"), + CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM, /* 0xC09D */ + "AES256-CCM"), + CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM_8, /* 0xC0A0 */ + "AES128-CCM8"), + CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM_8, /* 0xC0A1 */ + "AES256-CCM8"), + + /* RFC 7251 TLS 1.2 ECC CCM + Supported since BearSSL 0.6 */ + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM, /* 0xC0AC */ + "ECDHE-ECDSA-AES128-CCM"), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM, /* 0xC0AD */ + "ECDHE-ECDSA-AES256-CCM"), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, /* 0xC0AE */ + "ECDHE-ECDSA-AES128-CCM8"), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, /* 0xC0AF */ + "ECDHE-ECDSA-AES256-CCM8"), +#endif + + /* RFC 7905 TLS 1.2 ChaCha20-Poly1305 + Supported since BearSSL 0.2 */ + CIPHER_DEF(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ + "ECDHE-RSA-CHACHA20-POLY1305"), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ + "ECDHE-ECDSA-CHACHA20-POLY1305"), +}; + +#define NUM_OF_CIPHERS (sizeof(ciphertable) / sizeof(ciphertable[0])) +#define CIPHER_NAME_BUF_LEN 64 + +static bool is_separator(char c) +{ + /* Return whether character is a cipher list separator. */ + switch(c) { + case ' ': + case '\t': + case ':': + case ',': + case ';': + return true; + } + return false; +} + +static CURLcode bearssl_set_selected_ciphers(struct Curl_easy *data, + br_ssl_engine_context *ssl_eng, + const char *ciphers) +{ + uint16_t selected_ciphers[NUM_OF_CIPHERS]; + size_t selected_count = 0; + char cipher_name[CIPHER_NAME_BUF_LEN]; + const char *cipher_start = ciphers; + const char *cipher_end; + size_t i, j; + + if(!cipher_start) + return CURLE_SSL_CIPHER; + + while(true) { + /* Extract the next cipher name from the ciphers string */ + while(is_separator(*cipher_start)) + ++cipher_start; + if(*cipher_start == '\0') + break; + cipher_end = cipher_start; + while(*cipher_end != '\0' && !is_separator(*cipher_end)) + ++cipher_end; + j = cipher_end - cipher_start < CIPHER_NAME_BUF_LEN - 1 ? + cipher_end - cipher_start : CIPHER_NAME_BUF_LEN - 1; + strncpy(cipher_name, cipher_start, j); + cipher_name[j] = '\0'; + cipher_start = cipher_end; + + /* Lookup the cipher name in the table of available ciphers. If the cipher + name starts with "TLS_" we do the lookup by IANA name. Otherwise, we try + to match cipher name by an (OpenSSL) alias. */ + if(strncasecompare(cipher_name, "TLS_", 4)) { + for(i = 0; i < NUM_OF_CIPHERS && + !strcasecompare(cipher_name, ciphertable[i].name); ++i); + } + else { + for(i = 0; i < NUM_OF_CIPHERS && + !strcasecompare(cipher_name, ciphertable[i].alias_name); ++i); + } + if(i == NUM_OF_CIPHERS) { + infof(data, "BearSSL: unknown cipher in list: %s", cipher_name); + continue; + } + + /* No duplicates allowed */ + for(j = 0; j < selected_count && + selected_ciphers[j] != ciphertable[i].num; j++); + if(j < selected_count) { + infof(data, "BearSSL: duplicate cipher in list: %s", cipher_name); + continue; + } + + DEBUGASSERT(selected_count < NUM_OF_CIPHERS); + selected_ciphers[selected_count] = ciphertable[i].num; + ++selected_count; + } + + if(selected_count == 0) { + failf(data, "BearSSL: no supported cipher in list"); + return CURLE_SSL_CIPHER; + } + + br_ssl_engine_set_suites(ssl_eng, selected_ciphers, selected_count); + return CURLE_OK; +} + +static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + const char * const ssl_cafile = + /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ + (ca_info_blob ? NULL : conn_config->CAfile); + const char *hostname = connssl->peer.hostname; + const bool verifypeer = conn_config->verifypeer; + const bool verifyhost = conn_config->verifyhost; + CURLcode ret; + unsigned version_min, version_max; + int session_set = 0; + + DEBUGASSERT(backend); + CURL_TRC_CF(data, cf, "connect_step1"); + + switch(conn_config->version) { + case CURL_SSLVERSION_SSLv2: + failf(data, "BearSSL does not support SSLv2"); + return CURLE_SSL_CONNECT_ERROR; + case CURL_SSLVERSION_SSLv3: + failf(data, "BearSSL does not support SSLv3"); + return CURLE_SSL_CONNECT_ERROR; + case CURL_SSLVERSION_TLSv1_0: + version_min = BR_TLS10; + version_max = BR_TLS10; + break; + case CURL_SSLVERSION_TLSv1_1: + version_min = BR_TLS11; + version_max = BR_TLS11; + break; + case CURL_SSLVERSION_TLSv1_2: + version_min = BR_TLS12; + version_max = BR_TLS12; + break; + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + version_min = BR_TLS10; + version_max = BR_TLS12; + break; + default: + failf(data, "BearSSL: unknown CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(verifypeer) { + if(ca_info_blob) { + struct cafile_source source; + source.type = CAFILE_SOURCE_BLOB; + source.data = ca_info_blob->data; + source.len = ca_info_blob->len; + + CURL_TRC_CF(data, cf, "connect_step1, load ca_info_blob"); + ret = load_cafile(&source, &backend->anchors, &backend->anchors_len); + if(ret != CURLE_OK) { + failf(data, "error importing CA certificate blob"); + return ret; + } + } + + if(ssl_cafile) { + struct cafile_source source; + source.type = CAFILE_SOURCE_PATH; + source.data = ssl_cafile; + source.len = 0; + + CURL_TRC_CF(data, cf, "connect_step1, load cafile"); + ret = load_cafile(&source, &backend->anchors, &backend->anchors_len); + if(ret != CURLE_OK) { + failf(data, "error setting certificate verify locations." + " CAfile: %s", ssl_cafile); + return ret; + } + } + } + + /* initialize SSL context */ + br_ssl_client_init_full(&backend->ctx, &backend->x509.minimal, + backend->anchors, backend->anchors_len); + br_ssl_engine_set_versions(&backend->ctx.eng, version_min, version_max); + br_ssl_engine_set_buffer(&backend->ctx.eng, backend->buf, + sizeof(backend->buf), 1); + + if(conn_config->cipher_list) { + /* Override the ciphers as specified. For the default cipher list see the + BearSSL source code of br_ssl_client_init_full() */ + CURL_TRC_CF(data, cf, "connect_step1, set ciphers"); + ret = bearssl_set_selected_ciphers(data, &backend->ctx.eng, + conn_config->cipher_list); + if(ret) + return ret; + } + + /* initialize X.509 context */ + backend->x509.vtable = &x509_vtable; + backend->x509.verifypeer = verifypeer; + backend->x509.verifyhost = verifyhost; + br_ssl_engine_set_x509(&backend->ctx.eng, &backend->x509.vtable); + + if(ssl_config->primary.sessionid) { + void *session; + + CURL_TRC_CF(data, cf, "connect_step1, check session cache"); + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, &session, NULL)) { + br_ssl_engine_set_session_parameters(&backend->ctx.eng, session); + session_set = 1; + infof(data, "BearSSL: reusing session ID"); + } + Curl_ssl_sessionid_unlock(data); + } + + if(connssl->alpn) { + struct alpn_proto_buf proto; + size_t i; + + for(i = 0; i < connssl->alpn->count; ++i) { + backend->protocols[i] = connssl->alpn->entries[i]; + } + br_ssl_engine_set_protocol_names(&backend->ctx.eng, backend->protocols, + connssl->alpn->count); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } + + if(connssl->peer.is_ip_address) { + if(verifyhost) { + failf(data, "BearSSL: " + "host verification of IP address is not supported"); + return CURLE_PEER_FAILED_VERIFICATION; + } + hostname = NULL; + } + else { + if(!connssl->peer.sni) { + failf(data, "Failed to set SNI"); + return CURLE_SSL_CONNECT_ERROR; + } + hostname = connssl->peer.sni; + CURL_TRC_CF(data, cf, "connect_step1, SNI set"); + } + + /* give application a chance to interfere with SSL set up. */ + if(data->set.ssl.fsslctx) { + Curl_set_in_callback(data, true); + ret = (*data->set.ssl.fsslctx)(data, &backend->ctx, + data->set.ssl.fsslctxp); + Curl_set_in_callback(data, false); + if(ret) { + failf(data, "BearSSL: error signaled by ssl ctx callback"); + return ret; + } + } + + if(!br_ssl_client_reset(&backend->ctx, hostname, session_set)) + return CURLE_FAILED_INIT; + backend->active = TRUE; + + connssl->connecting_state = ssl_connect_2; + + return CURLE_OK; +} + +static void bearssl_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + if(!cf->connected) { + curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); + if(sock != CURL_SOCKET_BAD) { + struct ssl_connect_data *connssl = cf->ctx; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + unsigned state = br_ssl_engine_current_state(&backend->ctx.eng); + + if(state & BR_SSL_SENDREC) { + Curl_pollset_set_out_only(data, ps, sock); + } + else { + Curl_pollset_set_in_only(data, ps, sock); + } + } + } +} + +static CURLcode bearssl_run_until(struct Curl_cfilter *cf, + struct Curl_easy *data, + unsigned target) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + unsigned state; + unsigned char *buf; + size_t len; + ssize_t ret; + CURLcode result; + int err; + + DEBUGASSERT(backend); + + for(;;) { + state = br_ssl_engine_current_state(&backend->ctx.eng); + if(state & BR_SSL_CLOSED) { + err = br_ssl_engine_last_error(&backend->ctx.eng); + switch(err) { + case BR_ERR_OK: + /* TLS close notify */ + if(connssl->state != ssl_connection_complete) { + failf(data, "SSL: connection closed during handshake"); + return CURLE_SSL_CONNECT_ERROR; + } + return CURLE_OK; + case BR_ERR_X509_EXPIRED: + failf(data, "SSL: X.509 verification: " + "certificate is expired or not yet valid"); + return CURLE_PEER_FAILED_VERIFICATION; + case BR_ERR_X509_BAD_SERVER_NAME: + failf(data, "SSL: X.509 verification: " + "expected server name was not found in the chain"); + return CURLE_PEER_FAILED_VERIFICATION; + case BR_ERR_X509_NOT_TRUSTED: + failf(data, "SSL: X.509 verification: " + "chain could not be linked to a trust anchor"); + return CURLE_PEER_FAILED_VERIFICATION; + } + /* X.509 errors are documented to have the range 32..63 */ + if(err >= 32 && err < 64) + return CURLE_PEER_FAILED_VERIFICATION; + return CURLE_SSL_CONNECT_ERROR; + } + if(state & target) + return CURLE_OK; + if(state & BR_SSL_SENDREC) { + buf = br_ssl_engine_sendrec_buf(&backend->ctx.eng, &len); + ret = Curl_conn_cf_send(cf->next, data, (char *)buf, len, &result); + CURL_TRC_CF(data, cf, "ssl_send(len=%zu) -> %zd, %d", len, ret, result); + if(ret <= 0) { + return result; + } + br_ssl_engine_sendrec_ack(&backend->ctx.eng, ret); + } + else if(state & BR_SSL_RECVREC) { + buf = br_ssl_engine_recvrec_buf(&backend->ctx.eng, &len); + ret = Curl_conn_cf_recv(cf->next, data, (char *)buf, len, &result); + CURL_TRC_CF(data, cf, "ssl_recv(len=%zu) -> %zd, %d", len, ret, result); + if(ret == 0) { + failf(data, "SSL: EOF without close notify"); + return CURLE_READ_ERROR; + } + if(ret <= 0) { + return result; + } + br_ssl_engine_recvrec_ack(&backend->ctx.eng, ret); + } + } +} + +static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + CURLcode ret; + + DEBUGASSERT(backend); + CURL_TRC_CF(data, cf, "connect_step2"); + + ret = bearssl_run_until(cf, data, BR_SSL_SENDAPP | BR_SSL_RECVAPP); + if(ret == CURLE_AGAIN) + return CURLE_OK; + if(ret == CURLE_OK) { + unsigned int tver; + if(br_ssl_engine_current_state(&backend->ctx.eng) == BR_SSL_CLOSED) { + failf(data, "SSL: connection closed during handshake"); + return CURLE_SSL_CONNECT_ERROR; + } + connssl->connecting_state = ssl_connect_3; + /* Informational message */ + tver = br_ssl_engine_get_version(&backend->ctx.eng); + if(tver == 0x0303) + infof(data, "SSL connection using TLSv1.2"); + else if(tver == 0x0304) + infof(data, "SSL connection using TLSv1.3"); + else + infof(data, "SSL connection using TLS 0x%x", tver); + } + return ret; +} + +static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + CURLcode ret; + + DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + DEBUGASSERT(backend); + CURL_TRC_CF(data, cf, "connect_step3"); + + if(connssl->alpn) { + const char *proto; + + proto = br_ssl_engine_get_selected_protocol(&backend->ctx.eng); + Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto, + proto? strlen(proto) : 0); + } + + if(ssl_config->primary.sessionid) { + bool incache; + bool added = FALSE; + void *oldsession; + br_ssl_session_parameters *session; + + session = malloc(sizeof(*session)); + if(!session) + return CURLE_OUT_OF_MEMORY; + br_ssl_engine_get_session_parameters(&backend->ctx.eng, session); + Curl_ssl_sessionid_lock(data); + incache = !(Curl_ssl_getsessionid(cf, data, &oldsession, NULL)); + if(incache) + Curl_ssl_delsessionid(data, oldsession); + ret = Curl_ssl_addsessionid(cf, data, session, 0, &added); + Curl_ssl_sessionid_unlock(data); + if(!added) + free(session); + if(ret) { + return CURLE_OUT_OF_MEMORY; + } + } + + connssl->connecting_state = ssl_connect_done; + + return CURLE_OK; +} + +static ssize_t bearssl_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + unsigned char *app; + size_t applen; + + DEBUGASSERT(backend); + + for(;;) { + *err = bearssl_run_until(cf, data, BR_SSL_SENDAPP); + if(*err) + return -1; + app = br_ssl_engine_sendapp_buf(&backend->ctx.eng, &applen); + if(!app) { + failf(data, "SSL: connection closed during write"); + *err = CURLE_SEND_ERROR; + return -1; + } + if(backend->pending_write) { + applen = backend->pending_write; + backend->pending_write = 0; + return applen; + } + if(applen > len) + applen = len; + memcpy(app, buf, applen); + br_ssl_engine_sendapp_ack(&backend->ctx.eng, applen); + br_ssl_engine_flush(&backend->ctx.eng, 0); + backend->pending_write = applen; + } +} + +static ssize_t bearssl_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + unsigned char *app; + size_t applen; + + DEBUGASSERT(backend); + + *err = bearssl_run_until(cf, data, BR_SSL_RECVAPP); + if(*err != CURLE_OK) + return -1; + app = br_ssl_engine_recvapp_buf(&backend->ctx.eng, &applen); + if(!app) + return 0; + if(applen > len) + applen = len; + memcpy(buf, app, applen); + br_ssl_engine_recvapp_ack(&backend->ctx.eng, applen); + + return applen; +} + +static CURLcode bearssl_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool nonblocking, + bool *done) +{ + CURLcode ret; + struct ssl_connect_data *connssl = cf->ctx; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + timediff_t timeout_ms; + int what; + + CURL_TRC_CF(data, cf, "connect_common(blocking=%d)", !nonblocking); + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + CURL_TRC_CF(data, cf, "connect_common, connected"); + *done = TRUE; + return CURLE_OK; + } + + if(ssl_connect_1 == connssl->connecting_state) { + ret = bearssl_connect_step1(cf, data); + if(ret) + return ret; + } + + while(ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + /* check allowed time left */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + if(ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + + curl_socket_t writefd = ssl_connect_2_writing == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + + CURL_TRC_CF(data, cf, "connect_common, check socket"); + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, + nonblocking?0:timeout_ms); + CURL_TRC_CF(data, cf, "connect_common, check socket -> %d", what); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) { + *done = FALSE; + return CURLE_OK; + } + else { + /* timeout */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + /* Run transaction, and return to the caller if it failed or if this + * connection is done nonblocking and this loop would execute again. This + * permits the owner of a multi handle to abort a connection attempt + * before step2 has completed while ensuring that a client using select() + * or epoll() will always have a valid fdset to wait on. + */ + ret = bearssl_connect_step2(cf, data); + if(ret || (nonblocking && + (ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state))) + return ret; + } + + if(ssl_connect_3 == connssl->connecting_state) { + ret = bearssl_connect_step3(cf, data); + if(ret) + return ret; + } + + if(ssl_connect_done == connssl->connecting_state) { + connssl->state = ssl_connection_complete; + *done = TRUE; + } + else + *done = FALSE; + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + + return CURLE_OK; +} + +static size_t bearssl_version(char *buffer, size_t size) +{ + return msnprintf(buffer, size, "BearSSL"); +} + +static bool bearssl_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct ssl_connect_data *ctx = cf->ctx; + struct bearssl_ssl_backend_data *backend; + + (void)data; + DEBUGASSERT(ctx && ctx->backend); + backend = (struct bearssl_ssl_backend_data *)ctx->backend; + return br_ssl_engine_current_state(&backend->ctx.eng) & BR_SSL_RECVAPP; +} + +static CURLcode bearssl_random(struct Curl_easy *data UNUSED_PARAM, + unsigned char *entropy, size_t length) +{ + static br_hmac_drbg_context ctx; + static bool seeded = FALSE; + + if(!seeded) { + br_prng_seeder seeder; + + br_hmac_drbg_init(&ctx, &br_sha256_vtable, NULL, 0); + seeder = br_prng_seeder_system(NULL); + if(!seeder || !seeder(&ctx.vtable)) + return CURLE_FAILED_INIT; + seeded = TRUE; + } + br_hmac_drbg_generate(&ctx, entropy, length); + + return CURLE_OK; +} + +static CURLcode bearssl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode ret; + bool done = FALSE; + + ret = bearssl_connect_common(cf, data, FALSE, &done); + if(ret) + return ret; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +static CURLcode bearssl_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + return bearssl_connect_common(cf, data, TRUE, done); +} + +static void *bearssl_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) +{ + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + DEBUGASSERT(backend); + return &backend->ctx; +} + +static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + size_t i; + + DEBUGASSERT(backend); + + if(backend->active) { + backend->active = FALSE; + br_ssl_engine_close(&backend->ctx.eng); + (void)bearssl_run_until(cf, data, BR_SSL_CLOSED); + } + if(backend->anchors) { + for(i = 0; i < backend->anchors_len; ++i) + free(backend->anchors[i].dn.data); + Curl_safefree(backend->anchors); + } +} + +static void bearssl_session_free(void *ptr) +{ + free(ptr); +} + +static CURLcode bearssl_sha256sum(const unsigned char *input, + size_t inputlen, + unsigned char *sha256sum, + size_t sha256len UNUSED_PARAM) +{ + br_sha256_context ctx; + + br_sha256_init(&ctx); + br_sha256_update(&ctx, input, inputlen); + br_sha256_out(&ctx, sha256sum); + return CURLE_OK; +} + +const struct Curl_ssl Curl_ssl_bearssl = { + { CURLSSLBACKEND_BEARSSL, "bearssl" }, /* info */ + SSLSUPP_CAINFO_BLOB | SSLSUPP_SSL_CTX | SSLSUPP_HTTPS_PROXY, + sizeof(struct bearssl_ssl_backend_data), + + Curl_none_init, /* init */ + Curl_none_cleanup, /* cleanup */ + bearssl_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + Curl_none_shutdown, /* shutdown */ + bearssl_data_pending, /* data_pending */ + bearssl_random, /* random */ + Curl_none_cert_status_request, /* cert_status_request */ + bearssl_connect, /* connect */ + bearssl_connect_nonblocking, /* connect_nonblocking */ + bearssl_adjust_pollset, /* adjust_pollset */ + bearssl_get_internals, /* get_internals */ + bearssl_close, /* close_one */ + Curl_none_close_all, /* close_all */ + bearssl_session_free, /* session_free */ + Curl_none_set_engine, /* set_engine */ + Curl_none_set_engine_default, /* set_engine_default */ + Curl_none_engines_list, /* engines_list */ + Curl_none_false_start, /* false_start */ + bearssl_sha256sum, /* sha256sum */ + NULL, /* associate_connection */ + NULL, /* disassociate_connection */ + NULL, /* free_multi_ssl_backend_data */ + bearssl_recv, /* recv decrypted data */ + bearssl_send, /* send data to encrypt */ +}; + +#endif /* USE_BEARSSL */ diff --git a/Utilities/cmcurl/lib/vtls/bearssl.h b/Utilities/cmcurl/lib/vtls/bearssl.h new file mode 100644 index 0000000..b3651b0 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/bearssl.h @@ -0,0 +1,34 @@ +#ifndef HEADER_CURL_BEARSSL_H +#define HEADER_CURL_BEARSSL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Michael Forney, <mforney@mforney.org> + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_BEARSSL + +extern const struct Curl_ssl Curl_ssl_bearssl; + +#endif /* USE_BEARSSL */ +#endif /* HEADER_CURL_BEARSSL_H */ diff --git a/Utilities/cmcurl/lib/vtls/gtls.c b/Utilities/cmcurl/lib/vtls/gtls.c new file mode 100644 index 0000000..4e337f5 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/gtls.c @@ -0,0 +1,1679 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Source file for all GnuTLS-specific code for the TLS/SSL layer. No code + * but vtls.c should ever call or use these functions. + * + * Note: don't use the GnuTLS' *_t variable type names in this source code, + * since they were not present in 1.0.X. + */ + +#include "curl_setup.h" + +#ifdef USE_GNUTLS + +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include <gnutls/crypto.h> +#include <nettle/sha2.h> + +#include "urldata.h" +#include "sendf.h" +#include "inet_pton.h" +#include "gtls.h" +#include "vtls.h" +#include "vtls_int.h" +#include "vauth/vauth.h" +#include "parsedate.h" +#include "connect.h" /* for the connect timeout */ +#include "select.h" +#include "strcase.h" +#include "warnless.h" +#include "x509asn1.h" +#include "multiif.h" +#include "curl_printf.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/* Enable GnuTLS debugging by defining GTLSDEBUG */ +/*#define GTLSDEBUG */ + +#ifdef GTLSDEBUG +static void tls_log_func(int level, const char *str) +{ + fprintf(stderr, "|<%d>| %s", level, str); +} +#endif +static bool gtls_inited = FALSE; + +#if !defined(GNUTLS_VERSION_NUMBER) || (GNUTLS_VERSION_NUMBER < 0x03010a) +#error "too old GnuTLS version" +#endif + +# include <gnutls/ocsp.h> + +struct gtls_ssl_backend_data { + struct gtls_instance gtls; +}; + +static ssize_t gtls_push(void *s, const void *buf, size_t blen) +{ + struct Curl_cfilter *cf = s; + struct ssl_connect_data *connssl = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nwritten; + CURLcode result; + + DEBUGASSERT(data); + nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); + if(nwritten < 0) { + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + gnutls_transport_set_errno(backend->gtls.session, + (CURLE_AGAIN == result)? EAGAIN : EINVAL); + nwritten = -1; + } + return nwritten; +} + +static ssize_t gtls_pull(void *s, void *buf, size_t blen) +{ + struct Curl_cfilter *cf = s; + struct ssl_connect_data *connssl = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nread; + CURLcode result; + + DEBUGASSERT(data); + nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); + if(nread < 0) { + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + gnutls_transport_set_errno(backend->gtls.session, + (CURLE_AGAIN == result)? EAGAIN : EINVAL); + nread = -1; + } + return nread; +} + +/* gtls_init() + * + * Global GnuTLS init, called from Curl_ssl_init(). This calls functions that + * are not thread-safe and thus this function itself is not thread-safe and + * must only be called from within curl_global_init() to keep the thread + * situation under control! + */ +static int gtls_init(void) +{ + int ret = 1; + if(!gtls_inited) { + ret = gnutls_global_init()?0:1; +#ifdef GTLSDEBUG + gnutls_global_set_log_function(tls_log_func); + gnutls_global_set_log_level(2); +#endif + gtls_inited = TRUE; + } + return ret; +} + +static void gtls_cleanup(void) +{ + if(gtls_inited) { + gnutls_global_deinit(); + gtls_inited = FALSE; + } +} + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void showtime(struct Curl_easy *data, + const char *text, + time_t stamp) +{ + struct tm buffer; + const struct tm *tm = &buffer; + char str[96]; + CURLcode result = Curl_gmtime(stamp, &buffer); + if(result) + return; + + msnprintf(str, + sizeof(str), + " %s: %s, %02d %s %4d %02d:%02d:%02d GMT", + text, + Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + infof(data, "%s", str); +} +#endif + +static gnutls_datum_t load_file(const char *file) +{ + FILE *f; + gnutls_datum_t loaded_file = { NULL, 0 }; + long filelen; + void *ptr; + + f = fopen(file, "rb"); + if(!f) + return loaded_file; + if(fseek(f, 0, SEEK_END) != 0 + || (filelen = ftell(f)) < 0 + || fseek(f, 0, SEEK_SET) != 0 + || !(ptr = malloc((size_t)filelen))) + goto out; + if(fread(ptr, 1, (size_t)filelen, f) < (size_t)filelen) { + free(ptr); + goto out; + } + + loaded_file.data = ptr; + loaded_file.size = (unsigned int)filelen; +out: + fclose(f); + return loaded_file; +} + +static void unload_file(gnutls_datum_t data) +{ + free(data.data); +} + + +/* this function does a SSL/TLS (re-)handshake */ +static CURLcode handshake(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool duringconnect, + bool nonblocking) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + gnutls_session_t session; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + + DEBUGASSERT(backend); + session = backend->gtls.session; + + for(;;) { + timediff_t timeout_ms; + int rc; + + /* check allowed time left */ + timeout_ms = Curl_timeleft(data, NULL, duringconnect); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + if(connssl->connecting_state == ssl_connect_2_reading + || connssl->connecting_state == ssl_connect_2_writing) { + int what; + curl_socket_t writefd = ssl_connect_2_writing == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, + nonblocking?0: + timeout_ms?timeout_ms:1000); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) + return CURLE_OK; + else if(timeout_ms) { + /* timeout */ + failf(data, "SSL connection timeout at %ld", (long)timeout_ms); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + rc = gnutls_handshake(session); + + if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { + connssl->connecting_state = + gnutls_record_get_direction(session)? + ssl_connect_2_writing:ssl_connect_2_reading; + continue; + } + else if((rc < 0) && !gnutls_error_is_fatal(rc)) { + const char *strerr = NULL; + + if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) { + int alert = gnutls_alert_get(session); + strerr = gnutls_alert_get_name(alert); + } + + if(!strerr) + strerr = gnutls_strerror(rc); + + infof(data, "gnutls_handshake() warning: %s", strerr); + continue; + } + else if(rc < 0) { + const char *strerr = NULL; + + if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) { + int alert = gnutls_alert_get(session); + strerr = gnutls_alert_get_name(alert); + } + + if(!strerr) + strerr = gnutls_strerror(rc); + + failf(data, "gnutls_handshake() failed: %s", strerr); + return CURLE_SSL_CONNECT_ERROR; + } + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + return CURLE_OK; + } +} + +static gnutls_x509_crt_fmt_t do_file_type(const char *type) +{ + if(!type || !type[0]) + return GNUTLS_X509_FMT_PEM; + if(strcasecompare(type, "PEM")) + return GNUTLS_X509_FMT_PEM; + if(strcasecompare(type, "DER")) + return GNUTLS_X509_FMT_DER; + return GNUTLS_X509_FMT_PEM; /* default to PEM */ +} + +#define GNUTLS_CIPHERS "NORMAL:-ARCFOUR-128:-CTYPE-ALL:+CTYPE-X509" +/* If GnuTLS was compiled without support for SRP it will error out if SRP is + requested in the priority string, so treat it specially + */ +#define GNUTLS_SRP "+SRP" + +static CURLcode +set_ssl_version_min_max(struct Curl_easy *data, + struct ssl_primary_config *conn_config, + const char **prioritylist, + const char *tls13support) +{ + long ssl_version = conn_config->version; + long ssl_version_max = conn_config->version_max; + + if((ssl_version == CURL_SSLVERSION_DEFAULT) || + (ssl_version == CURL_SSLVERSION_TLSv1)) + ssl_version = CURL_SSLVERSION_TLSv1_0; + if(ssl_version_max == CURL_SSLVERSION_MAX_NONE) + ssl_version_max = CURL_SSLVERSION_MAX_DEFAULT; + if(!tls13support) { + /* If the running GnuTLS doesn't support TLS 1.3, we must not specify a + prioritylist involving that since it will make GnuTLS return an en + error back at us */ + if((ssl_version_max == CURL_SSLVERSION_MAX_TLSv1_3) || + (ssl_version_max == CURL_SSLVERSION_MAX_DEFAULT)) { + ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; + } + } + else if(ssl_version_max == CURL_SSLVERSION_MAX_DEFAULT) { + ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_3; + } + + switch(ssl_version | ssl_version_max) { + case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_0: + *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" + "+VERS-TLS1.0"; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_1: + *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" + "+VERS-TLS1.1:+VERS-TLS1.0"; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_2: + *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" + "+VERS-TLS1.2:+VERS-TLS1.1:+VERS-TLS1.0"; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_1: + *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" + "+VERS-TLS1.1"; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_2: + *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" + "+VERS-TLS1.2:+VERS-TLS1.1"; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_2: + *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" + "+VERS-TLS1.2"; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_3 | CURL_SSLVERSION_MAX_TLSv1_3: + *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" + "+VERS-TLS1.3"; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_3: + *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0"; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_3: + *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" + "+VERS-TLS1.3:+VERS-TLS1.2:+VERS-TLS1.1"; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_3: + *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" + "+VERS-TLS1.3:+VERS-TLS1.2"; + return CURLE_OK; + } + + failf(data, "GnuTLS: cannot set ssl protocol"); + return CURLE_SSL_CONNECT_ERROR; +} + +CURLcode gtls_client_init(struct Curl_easy *data, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + struct ssl_peer *peer, + struct gtls_instance *gtls, + long *pverifyresult) +{ + unsigned int init_flags; + int rc; + bool sni = TRUE; /* default is SNI enabled */ + const char *prioritylist; + const char *err = NULL; + const char *tls13support; + CURLcode result; + + if(!gtls_inited) + gtls_init(); + + *pverifyresult = 0; + + if(config->version == CURL_SSLVERSION_SSLv2) { + failf(data, "GnuTLS does not support SSLv2"); + return CURLE_SSL_CONNECT_ERROR; + } + else if(config->version == CURL_SSLVERSION_SSLv3) + sni = FALSE; /* SSLv3 has no SNI */ + + /* allocate a cred struct */ + rc = gnutls_certificate_allocate_credentials(>ls->cred); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; + } + +#ifdef USE_GNUTLS_SRP + if(config->username && Curl_auth_allowed_to_host(data)) { + infof(data, "Using TLS-SRP username: %s", config->username); + + rc = gnutls_srp_allocate_client_credentials(>ls->srp_client_cred); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_srp_allocate_client_cred() failed: %s", + gnutls_strerror(rc)); + return CURLE_OUT_OF_MEMORY; + } + + rc = gnutls_srp_set_client_credentials(gtls->srp_client_cred, + config->username, + config->password); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_srp_set_client_cred() failed: %s", + gnutls_strerror(rc)); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } +#endif + + if(config->verifypeer) { + bool imported_native_ca = false; + + if(ssl_config->native_ca_store) { + rc = gnutls_certificate_set_x509_system_trust(gtls->cred); + if(rc < 0) + infof(data, "error reading native ca store (%s), continuing anyway", + gnutls_strerror(rc)); + else { + infof(data, "found %d certificates in native ca store", rc); + if(rc > 0) + imported_native_ca = true; + } + } + + if(config->CAfile) { + /* set the trusted CA cert bundle file */ + gnutls_certificate_set_verify_flags(gtls->cred, + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); + + rc = gnutls_certificate_set_x509_trust_file(gtls->cred, + config->CAfile, + GNUTLS_X509_FMT_PEM); + if(rc < 0) { + infof(data, "error reading ca cert file %s (%s)%s", + config->CAfile, gnutls_strerror(rc), + (imported_native_ca ? ", continuing anyway" : "")); + if(!imported_native_ca) { + *pverifyresult = rc; + return CURLE_SSL_CACERT_BADFILE; + } + } + else + infof(data, "found %d certificates in %s", rc, config->CAfile); + } + + if(config->CApath) { + /* set the trusted CA cert directory */ + rc = gnutls_certificate_set_x509_trust_dir(gtls->cred, + config->CApath, + GNUTLS_X509_FMT_PEM); + if(rc < 0) { + infof(data, "error reading ca cert file %s (%s)%s", + config->CApath, gnutls_strerror(rc), + (imported_native_ca ? ", continuing anyway" : "")); + if(!imported_native_ca) { + *pverifyresult = rc; + return CURLE_SSL_CACERT_BADFILE; + } + } + else + infof(data, "found %d certificates in %s", rc, config->CApath); + } + } + + if(config->CRLfile) { + /* set the CRL list file */ + rc = gnutls_certificate_set_x509_crl_file(gtls->cred, + config->CRLfile, + GNUTLS_X509_FMT_PEM); + if(rc < 0) { + failf(data, "error reading crl file %s (%s)", + config->CRLfile, gnutls_strerror(rc)); + return CURLE_SSL_CRL_BADFILE; + } + else + infof(data, "found %d CRL in %s", rc, config->CRLfile); + } + + /* Initialize TLS session as a client */ + init_flags = GNUTLS_CLIENT; + +#if defined(GNUTLS_FORCE_CLIENT_CERT) + init_flags |= GNUTLS_FORCE_CLIENT_CERT; +#endif + +#if defined(GNUTLS_NO_TICKETS) + /* Disable TLS session tickets */ + init_flags |= GNUTLS_NO_TICKETS; +#endif + + rc = gnutls_init(>ls->session, init_flags); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_init() failed: %d", rc); + return CURLE_SSL_CONNECT_ERROR; + } + + if(sni && peer->sni) { + if(gnutls_server_name_set(gtls->session, GNUTLS_NAME_DNS, + peer->sni, strlen(peer->sni)) < 0) { + failf(data, "Failed to set SNI"); + return CURLE_SSL_CONNECT_ERROR; + } + } + + /* Use default priorities */ + rc = gnutls_set_default_priority(gtls->session); + if(rc != GNUTLS_E_SUCCESS) + return CURLE_SSL_CONNECT_ERROR; + + /* "In GnuTLS 3.6.5, TLS 1.3 is enabled by default" */ + tls13support = gnutls_check_version("3.6.5"); + + /* Ensure +SRP comes at the *end* of all relevant strings so that it can be + * removed if a run-time error indicates that SRP is not supported by this + * GnuTLS version */ + + if(config->version == CURL_SSLVERSION_SSLv2 || + config->version == CURL_SSLVERSION_SSLv3) { + failf(data, "GnuTLS does not support SSLv2 or SSLv3"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(config->version == CURL_SSLVERSION_TLSv1_3) { + if(!tls13support) { + failf(data, "This GnuTLS installation does not support TLS 1.3"); + return CURLE_SSL_CONNECT_ERROR; + } + } + + /* At this point we know we have a supported TLS version, so set it */ + result = set_ssl_version_min_max(data, config, &prioritylist, tls13support); + if(result) + return result; + +#ifdef USE_GNUTLS_SRP + /* Only add SRP to the cipher list if SRP is requested. Otherwise + * GnuTLS will disable TLS 1.3 support. */ + if(config->username) { + size_t len = strlen(prioritylist); + + char *prioritysrp = malloc(len + sizeof(GNUTLS_SRP) + 1); + if(!prioritysrp) + return CURLE_OUT_OF_MEMORY; + strcpy(prioritysrp, prioritylist); + strcpy(prioritysrp + len, ":" GNUTLS_SRP); + rc = gnutls_priority_set_direct(gtls->session, prioritysrp, &err); + free(prioritysrp); + + if((rc == GNUTLS_E_INVALID_REQUEST) && err) { + infof(data, "This GnuTLS does not support SRP"); + } + } + else { +#endif + infof(data, "GnuTLS ciphers: %s", prioritylist); + rc = gnutls_priority_set_direct(gtls->session, prioritylist, &err); +#ifdef USE_GNUTLS_SRP + } +#endif + + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "Error %d setting GnuTLS cipher list starting with %s", + rc, err); + return CURLE_SSL_CONNECT_ERROR; + } + + if(config->clientcert) { + if(ssl_config->key_passwd) { + const unsigned int supported_key_encryption_algorithms = + GNUTLS_PKCS_USE_PKCS12_3DES | GNUTLS_PKCS_USE_PKCS12_ARCFOUR | + GNUTLS_PKCS_USE_PKCS12_RC2_40 | GNUTLS_PKCS_USE_PBES2_3DES | + GNUTLS_PKCS_USE_PBES2_AES_128 | GNUTLS_PKCS_USE_PBES2_AES_192 | + GNUTLS_PKCS_USE_PBES2_AES_256; + rc = gnutls_certificate_set_x509_key_file2( + gtls->cred, + config->clientcert, + ssl_config->key ? ssl_config->key : config->clientcert, + do_file_type(ssl_config->cert_type), + ssl_config->key_passwd, + supported_key_encryption_algorithms); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, + "error reading X.509 potentially-encrypted key file: %s", + gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { + if(gnutls_certificate_set_x509_key_file( + gtls->cred, + config->clientcert, + ssl_config->key ? ssl_config->key : config->clientcert, + do_file_type(ssl_config->cert_type) ) != + GNUTLS_E_SUCCESS) { + failf(data, "error reading X.509 key or certificate file"); + return CURLE_SSL_CONNECT_ERROR; + } + } + } + +#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->cred); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; + } + } + + if(config->verifystatus) { + rc = gnutls_ocsp_status_request_enable_client(gtls->session, + NULL, 0, NULL); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_ocsp_status_request_enable_client() failed: %d", rc); + return CURLE_SSL_CONNECT_ERROR; + } + } + + return CURLE_OK; +} + +static CURLcode +gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + long * const pverifyresult = &ssl_config->certverifyresult; + CURLcode result; + + DEBUGASSERT(backend); + + if(connssl->state == ssl_connection_complete) + /* to make us tolerant against being called more than once for the + same connection */ + return CURLE_OK; + + result = gtls_client_init(data, conn_config, ssl_config, + &connssl->peer, + &backend->gtls, pverifyresult); + if(result) + return result; + + if(connssl->alpn) { + struct alpn_proto_buf proto; + gnutls_datum_t alpn[ALPN_ENTRIES_MAX]; + size_t i; + + for(i = 0; i < connssl->alpn->count; ++i) { + alpn[i].data = (unsigned char *)connssl->alpn->entries[i]; + alpn[i].size = (unsigned)strlen(connssl->alpn->entries[i]); + } + if(gnutls_alpn_set_protocols(backend->gtls.session, alpn, + (unsigned)connssl->alpn->count, 0)) { + failf(data, "failed setting ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } + + /* This might be a reconnect, so we check for a session ID in the cache + to speed up things */ + if(conn_config->sessionid) { + void *ssl_sessionid; + size_t ssl_idsize; + + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, &ssl_idsize)) { + /* we got a session id, use it! */ + gnutls_session_set_data(backend->gtls.session, + ssl_sessionid, ssl_idsize); + + /* Informational message */ + infof(data, "SSL reusing session ID"); + } + Curl_ssl_sessionid_unlock(data); + } + + /* register callback functions and handle to send and receive data. */ + gnutls_transport_set_ptr(backend->gtls.session, cf); + gnutls_transport_set_push_function(backend->gtls.session, gtls_push); + gnutls_transport_set_pull_function(backend->gtls.session, gtls_pull); + + return CURLE_OK; +} + +static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, + gnutls_x509_crt_t cert, + const char *pinnedpubkey) +{ + /* Scratch */ + size_t len1 = 0, len2 = 0; + unsigned char *buff1 = NULL; + + gnutls_pubkey_t key = NULL; + + /* Result is returned to caller */ + CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; + + /* if a path wasn't specified, don't pin */ + if(!pinnedpubkey) + return CURLE_OK; + + if(!cert) + return result; + + do { + int ret; + + /* Begin Gyrations to get the public key */ + gnutls_pubkey_init(&key); + + ret = gnutls_pubkey_import_x509(key, cert, 0); + if(ret < 0) + break; /* failed */ + + ret = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, NULL, &len1); + if(ret != GNUTLS_E_SHORT_MEMORY_BUFFER || len1 == 0) + break; /* failed */ + + buff1 = malloc(len1); + if(!buff1) + break; /* failed */ + + len2 = len1; + + ret = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, buff1, &len2); + if(ret < 0 || len1 != len2) + break; /* failed */ + + /* End Gyrations */ + + /* The one good exit point */ + result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1); + } while(0); + + if(key) + gnutls_pubkey_deinit(key); + + Curl_safefree(buff1); + + return result; +} + +CURLcode +Curl_gtls_verifyserver(struct Curl_easy *data, + gnutls_session_t session, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + struct ssl_peer *peer, + const char *pinned_key) +{ + unsigned int cert_list_size; + const gnutls_datum_t *chainp; + unsigned int verify_status = 0; + gnutls_x509_crt_t x509_cert, x509_issuer; + gnutls_datum_t issuerp; + gnutls_datum_t certfields; + char certname[65] = ""; /* limited to 64 chars by ASN.1 */ + size_t size; + time_t certclock; + const char *ptr; + int rc; + CURLcode result = CURLE_OK; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + unsigned int algo; + unsigned int bits; + gnutls_protocol_t version = gnutls_protocol_get_version(session); +#endif + long * const certverifyresult = &ssl_config->certverifyresult; + + /* the name of the cipher suite used, e.g. ECDHE_RSA_AES_256_GCM_SHA384. */ + ptr = gnutls_cipher_suite_get_name(gnutls_kx_get(session), + gnutls_cipher_get(session), + gnutls_mac_get(session)); + + infof(data, "SSL connection using %s / %s", + gnutls_protocol_get_name(version), ptr); + + /* This function will return the peer's raw certificate (chain) as sent by + the peer. These certificates are in raw format (DER encoded for + X.509). In case of a X.509 then a certificate list may be present. The + first certificate in the list is the peer's certificate, following the + issuer's certificate, then the issuer's issuer etc. */ + + chainp = gnutls_certificate_get_peers(session, &cert_list_size); + if(!chainp) { + 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; + return CURLE_PEER_FAILED_VERIFICATION; +#ifdef USE_GNUTLS_SRP + } +#endif + } + infof(data, " common name: WARNING couldn't obtain"); + } + + if(data->set.ssl.certinfo && chainp) { + unsigned int i; + + result = Curl_ssl_init_certinfo(data, cert_list_size); + if(result) + return result; + + for(i = 0; i < cert_list_size; i++) { + const char *beg = (const char *) chainp[i].data; + const char *end = beg + chainp[i].size; + + result = Curl_extract_certinfo(data, i, beg, end); + if(result) + return result; + } + } + + if(config->verifypeer) { + /* This function will try to verify the peer's certificate and return its + status (trusted, invalid etc.). The value of status should be one or + more of the gnutls_certificate_status_t enumerated elements bitwise + or'd. To avoid denial of service attacks some default upper limits + regarding the certificate key size and chain size are set. To override + them use gnutls_certificate_set_verify_limits(). */ + + rc = gnutls_certificate_verify_peers2(session, &verify_status); + if(rc < 0) { + failf(data, "server cert verify failed: %d", rc); + *certverifyresult = rc; + return CURLE_SSL_CONNECT_ERROR; + } + + *certverifyresult = verify_status; + + /* verify_status is a bitmask of gnutls_certificate_status bits */ + if(verify_status & GNUTLS_CERT_INVALID) { + if(config->verifypeer) { + failf(data, "server certificate verification failed. CAfile: %s " + "CRLfile: %s", config->CAfile ? config->CAfile: + "none", + ssl_config->primary.CRLfile ? + ssl_config->primary.CRLfile : "none"); + return CURLE_PEER_FAILED_VERIFICATION; + } + else + infof(data, " server certificate verification FAILED"); + } + else + infof(data, " server certificate verification OK"); + } + else + infof(data, " server certificate verification SKIPPED"); + + if(config->verifystatus) { + if(gnutls_ocsp_status_request_is_checked(session, 0) == 0) { + gnutls_datum_t status_request; + gnutls_ocsp_resp_t ocsp_resp; + + gnutls_ocsp_cert_status_t status; + gnutls_x509_crl_reason_t reason; + + rc = gnutls_ocsp_status_request_get(session, &status_request); + + infof(data, " server certificate status verification FAILED"); + + if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + failf(data, "No OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } + + if(rc < 0) { + failf(data, "Invalid OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } + + gnutls_ocsp_resp_init(&ocsp_resp); + + rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request); + if(rc < 0) { + failf(data, "Invalid OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } + + (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, + &status, NULL, NULL, NULL, &reason); + + switch(status) { + case GNUTLS_OCSP_CERT_GOOD: + break; + + case GNUTLS_OCSP_CERT_REVOKED: { + const char *crl_reason; + + switch(reason) { + default: + case GNUTLS_X509_CRLREASON_UNSPECIFIED: + crl_reason = "unspecified reason"; + break; + + case GNUTLS_X509_CRLREASON_KEYCOMPROMISE: + crl_reason = "private key compromised"; + break; + + case GNUTLS_X509_CRLREASON_CACOMPROMISE: + crl_reason = "CA compromised"; + break; + + case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED: + crl_reason = "affiliation has changed"; + break; + + case GNUTLS_X509_CRLREASON_SUPERSEDED: + crl_reason = "certificate superseded"; + break; + + case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION: + crl_reason = "operation has ceased"; + break; + + case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD: + crl_reason = "certificate is on hold"; + break; + + case GNUTLS_X509_CRLREASON_REMOVEFROMCRL: + crl_reason = "will be removed from delta CRL"; + break; + + case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN: + crl_reason = "privilege withdrawn"; + break; + + case GNUTLS_X509_CRLREASON_AACOMPROMISE: + crl_reason = "AA compromised"; + break; + } + + failf(data, "Server certificate was revoked: %s", crl_reason); + break; + } + + default: + case GNUTLS_OCSP_CERT_UNKNOWN: + failf(data, "Server certificate status is unknown"); + break; + } + + gnutls_ocsp_resp_deinit(ocsp_resp); + + return CURLE_SSL_INVALIDCERTSTATUS; + } + else + infof(data, " server certificate status verification OK"); + } + else + infof(data, " server certificate status verification SKIPPED"); + + /* initialize an X.509 certificate structure. */ + gnutls_x509_crt_init(&x509_cert); + + if(chainp) + /* convert the given DER or PEM encoded Certificate to the native + gnutls_x509_crt_t format */ + gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER); + + if(config->issuercert) { + gnutls_x509_crt_init(&x509_issuer); + issuerp = load_file(config->issuercert); + gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM); + rc = gnutls_x509_crt_check_issuer(x509_cert, x509_issuer); + gnutls_x509_crt_deinit(x509_issuer); + unload_file(issuerp); + if(rc <= 0) { + failf(data, "server certificate issuer check failed (IssuerCert: %s)", + config->issuercert?config->issuercert:"none"); + gnutls_x509_crt_deinit(x509_cert); + return CURLE_SSL_ISSUER_ERROR; + } + infof(data, " server certificate issuer check OK (Issuer Cert: %s)", + config->issuercert?config->issuercert:"none"); + } + + size = sizeof(certname); + rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME, + 0, /* the first and only one */ + FALSE, + certname, + &size); + if(rc) { + infof(data, "error fetching CN from cert:%s", + gnutls_strerror(rc)); + } + + /* This function will check if the given certificate's subject matches the + given hostname. This is a basic implementation of the matching described + in RFC2818 (HTTPS), which takes into account wildcards, and the subject + alternative name PKIX extension. Returns non zero on success, and zero on + failure. */ + rc = gnutls_x509_crt_check_hostname(x509_cert, peer->hostname); +#if GNUTLS_VERSION_NUMBER < 0x030306 + /* Before 3.3.6, gnutls_x509_crt_check_hostname() didn't check IP + addresses. */ + if(!rc) { +#ifdef ENABLE_IPV6 + #define use_addr in6_addr +#else + #define use_addr in_addr +#endif + unsigned char addrbuf[sizeof(struct use_addr)]; + size_t addrlen = 0; + + if(Curl_inet_pton(AF_INET, peer->hostname, addrbuf) > 0) + addrlen = 4; +#ifdef ENABLE_IPV6 + else if(Curl_inet_pton(AF_INET6, peer->hostname, addrbuf) > 0) + addrlen = 16; +#endif + + if(addrlen) { + unsigned char certaddr[sizeof(struct use_addr)]; + int i; + + for(i = 0; ; i++) { + size_t certaddrlen = sizeof(certaddr); + int ret = gnutls_x509_crt_get_subject_alt_name(x509_cert, i, certaddr, + &certaddrlen, NULL); + /* If this happens, it wasn't an IP address. */ + if(ret == GNUTLS_E_SHORT_MEMORY_BUFFER) + continue; + if(ret < 0) + break; + if(ret != GNUTLS_SAN_IPADDRESS) + continue; + if(certaddrlen == addrlen && !memcmp(addrbuf, certaddr, addrlen)) { + rc = 1; + break; + } + } + } + } +#endif + if(!rc) { + if(config->verifyhost) { + failf(data, "SSL: certificate subject name (%s) does not match " + "target host name '%s'", certname, peer->dispname); + gnutls_x509_crt_deinit(x509_cert); + return CURLE_PEER_FAILED_VERIFICATION; + } + else + infof(data, " common name: %s (does not match '%s')", + certname, peer->dispname); + } + else + infof(data, " common name: %s (matched)", certname); + + /* Check for time-based validity */ + certclock = gnutls_x509_crt_get_expiration_time(x509_cert); + + if(certclock == (time_t)-1) { + if(config->verifypeer) { + failf(data, "server cert expiration date verify failed"); + *certverifyresult = GNUTLS_CERT_EXPIRED; + gnutls_x509_crt_deinit(x509_cert); + return CURLE_SSL_CONNECT_ERROR; + } + else + infof(data, " server certificate expiration date verify FAILED"); + } + else { + if(certclock < time(NULL)) { + if(config->verifypeer) { + failf(data, "server certificate expiration date has passed."); + *certverifyresult = GNUTLS_CERT_EXPIRED; + gnutls_x509_crt_deinit(x509_cert); + return CURLE_PEER_FAILED_VERIFICATION; + } + else + infof(data, " server certificate expiration date FAILED"); + } + else + infof(data, " server certificate expiration date OK"); + } + + certclock = gnutls_x509_crt_get_activation_time(x509_cert); + + if(certclock == (time_t)-1) { + if(config->verifypeer) { + failf(data, "server cert activation date verify failed"); + *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED; + gnutls_x509_crt_deinit(x509_cert); + return CURLE_SSL_CONNECT_ERROR; + } + else + infof(data, " server certificate activation date verify FAILED"); + } + else { + if(certclock > time(NULL)) { + if(config->verifypeer) { + failf(data, "server certificate not activated yet."); + *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED; + gnutls_x509_crt_deinit(x509_cert); + return CURLE_PEER_FAILED_VERIFICATION; + } + else + infof(data, " server certificate activation date FAILED"); + } + else + infof(data, " server certificate activation date OK"); + } + + if(pinned_key) { + result = pkp_pin_peer_pubkey(data, x509_cert, pinned_key); + if(result != CURLE_OK) { + failf(data, "SSL: public key does not match pinned public key"); + gnutls_x509_crt_deinit(x509_cert); + return result; + } + } + + /* Show: + + - subject + - start date + - expire date + - common name + - issuer + + */ + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + /* public key algorithm's parameters */ + algo = gnutls_x509_crt_get_pk_algorithm(x509_cert, &bits); + infof(data, " certificate public key: %s", + gnutls_pk_algorithm_get_name(algo)); + + /* version of the X.509 certificate. */ + infof(data, " certificate version: #%d", + gnutls_x509_crt_get_version(x509_cert)); + + + rc = gnutls_x509_crt_get_dn2(x509_cert, &certfields); + if(rc) + infof(data, "Failed to get certificate name"); + else { + infof(data, " subject: %s", certfields.data); + + certclock = gnutls_x509_crt_get_activation_time(x509_cert); + showtime(data, "start date", certclock); + + certclock = gnutls_x509_crt_get_expiration_time(x509_cert); + showtime(data, "expire date", certclock); + + gnutls_free(certfields.data); + } + + rc = gnutls_x509_crt_get_issuer_dn2(x509_cert, &certfields); + if(rc) + infof(data, "Failed to get certificate issuer"); + else { + infof(data, " issuer: %s", certfields.data); + + gnutls_free(certfields.data); + } +#endif + + gnutls_x509_crt_deinit(x509_cert); + + return result; +} + +static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_session_t session) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + const char *pinned_key = Curl_ssl_cf_is_proxy(cf)? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + CURLcode result; + + result = Curl_gtls_verifyserver(data, session, conn_config, ssl_config, + &connssl->peer, pinned_key); + if(result) + goto out; + + if(connssl->alpn) { + gnutls_datum_t proto; + int rc; + + rc = gnutls_alpn_get_selected_protocol(session, &proto); + if(rc == 0) + Curl_alpn_set_negotiated(cf, data, proto.data, proto.size); + else + Curl_alpn_set_negotiated(cf, data, NULL, 0); + } + + if(ssl_config->primary.sessionid) { + /* we always unconditionally get the session id here, as even if we + already got it from the cache and asked to use it in the connection, it + might've been rejected and then a new one is in use now and we need to + detect that. */ + void *connect_sessionid; + size_t connect_idsize = 0; + + /* get the session ID data size */ + gnutls_session_get_data(session, NULL, &connect_idsize); + connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ + + if(connect_sessionid) { + bool incache; + bool added = FALSE; + void *ssl_sessionid; + + /* extract session ID to the allocated buffer */ + gnutls_session_get_data(session, connect_sessionid, &connect_idsize); + + Curl_ssl_sessionid_lock(data); + incache = !(Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)); + if(incache) { + /* there was one before in the cache, so instead of risking that the + previous one was rejected, we just kill that and store the new */ + Curl_ssl_delsessionid(data, ssl_sessionid); + } + + /* store this session id */ + result = Curl_ssl_addsessionid(cf, data, connect_sessionid, + connect_idsize, &added); + Curl_ssl_sessionid_unlock(data); + if(!added) + free(connect_sessionid); + if(result) { + result = CURLE_OUT_OF_MEMORY; + } + } + else + result = CURLE_OUT_OF_MEMORY; + } + +out: + return result; +} + +/* + * This function is called after the TCP connect has completed. Setup the TLS + * layer and do all necessary magic. + */ +/* We use connssl->connecting_state to keep track of the connection status; + there are three states: 'ssl_connect_1' (not started yet or complete), + 'ssl_connect_2_reading' (waiting for data from server), and + 'ssl_connect_2_writing' (waiting to be able to write). + */ +static CURLcode +gtls_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool nonblocking, + bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + int rc; + CURLcode result = CURLE_OK; + + /* Initiate the connection, if not already done */ + if(ssl_connect_1 == connssl->connecting_state) { + rc = gtls_connect_step1(cf, data); + if(rc) { + result = rc; + goto out; + } + } + + rc = handshake(cf, data, TRUE, nonblocking); + if(rc) { + /* handshake() sets its own error message with failf() */ + result = rc; + goto out; + } + + /* Finish connecting once the handshake is done */ + if(ssl_connect_1 == connssl->connecting_state) { + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + gnutls_session_t session; + DEBUGASSERT(backend); + session = backend->gtls.session; + rc = gtls_verifyserver(cf, data, session); + if(rc) { + result = rc; + goto out; + } + connssl->state = ssl_connection_complete; + } + +out: + *done = ssl_connect_1 == connssl->connecting_state; + + return result; +} + +static CURLcode gtls_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + return gtls_connect_common(cf, data, TRUE, done); +} + +static CURLcode gtls_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result; + bool done = FALSE; + + result = gtls_connect_common(cf, data, FALSE, &done); + if(result) + return result; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +static bool gtls_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct ssl_connect_data *ctx = cf->ctx; + struct gtls_ssl_backend_data *backend; + + (void)data; + DEBUGASSERT(ctx && ctx->backend); + backend = (struct gtls_ssl_backend_data *)ctx->backend; + if(backend->gtls.session && + 0 != gnutls_record_check_pending(backend->gtls.session)) + return TRUE; + return FALSE; +} + +static ssize_t gtls_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, + size_t len, + CURLcode *curlcode) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + ssize_t rc; + + (void)data; + DEBUGASSERT(backend); + rc = gnutls_record_send(backend->gtls.session, mem, len); + + if(rc < 0) { + *curlcode = (rc == GNUTLS_E_AGAIN) + ? CURLE_AGAIN + : CURLE_SEND_ERROR; + + rc = -1; + } + + return rc; +} + +static void gtls_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + + (void) data; + DEBUGASSERT(backend); + + if(backend->gtls.session) { + char buf[32]; + /* Maybe the server has already sent a close notify alert. + Read it to avoid an RST on the TCP connection. */ + (void)gnutls_record_recv(backend->gtls.session, buf, sizeof(buf)); + gnutls_bye(backend->gtls.session, GNUTLS_SHUT_WR); + gnutls_deinit(backend->gtls.session); + backend->gtls.session = NULL; + } + if(backend->gtls.cred) { + gnutls_certificate_free_credentials(backend->gtls.cred); + backend->gtls.cred = NULL; + } +#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 +} + +/* + * This function is called to shut down the SSL layer but keep the + * socket open (CCC - Clear Command Channel) + */ +static int gtls_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + int retval = 0; + + DEBUGASSERT(backend); + +#ifndef CURL_DISABLE_FTP + /* This has only been tested on the proftpd server, and the mod_tls code + sends a close notify alert without waiting for a close notify alert in + response. Thus we wait for a close notify alert from the server, but + we do not send one. Let's hope other servers do the same... */ + + if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) + gnutls_bye(backend->gtls.session, GNUTLS_SHUT_WR); +#endif + + if(backend->gtls.session) { + ssize_t result; + bool done = FALSE; + char buf[120]; + + while(!done) { + int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), + SSL_SHUTDOWN_TIMEOUT); + if(what > 0) { + /* Something to read, let's do it and hope that it is the close + notify alert from the server */ + result = gnutls_record_recv(backend->gtls.session, + buf, sizeof(buf)); + switch(result) { + case 0: + /* This is the expected response. There was no data but only + the close notify alert */ + done = TRUE; + break; + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED"); + break; + default: + retval = -1; + done = TRUE; + break; + } + } + else if(0 == what) { + /* timeout */ + failf(data, "SSL shutdown timeout"); + done = TRUE; + } + else { + /* anything that gets here is fatally bad */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + retval = -1; + done = TRUE; + } + } + gnutls_deinit(backend->gtls.session); + } + gnutls_certificate_free_credentials(backend->gtls.cred); + +#ifdef USE_GNUTLS_SRP + { + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + if(ssl_config->primary.username) + gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred); + } +#endif + + backend->gtls.cred = NULL; + backend->gtls.session = NULL; + + return retval; +} + +static ssize_t gtls_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, + size_t buffersize, + CURLcode *curlcode) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + ssize_t ret; + + (void)data; + DEBUGASSERT(backend); + + ret = gnutls_record_recv(backend->gtls.session, buf, buffersize); + if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { + *curlcode = CURLE_AGAIN; + ret = -1; + goto out; + } + + if(ret == GNUTLS_E_REHANDSHAKE) { + /* BLOCKING call, this is bad but a work-around for now. Fixing this "the + proper way" takes a whole lot of work. */ + CURLcode result = handshake(cf, data, FALSE, FALSE); + if(result) + /* handshake() writes error message on its own */ + *curlcode = result; + else + *curlcode = CURLE_AGAIN; /* then return as if this was a wouldblock */ + ret = -1; + goto out; + } + + if(ret < 0) { + failf(data, "GnuTLS recv error (%d): %s", + + (int)ret, gnutls_strerror((int)ret)); + *curlcode = CURLE_RECV_ERROR; + ret = -1; + goto out; + } + +out: + return ret; +} + +static void gtls_session_free(void *ptr) +{ + free(ptr); +} + +static size_t gtls_version(char *buffer, size_t size) +{ + return msnprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL)); +} + +/* data might be NULL! */ +static CURLcode gtls_random(struct Curl_easy *data, + unsigned char *entropy, size_t length) +{ + int rc; + (void)data; + rc = gnutls_rnd(GNUTLS_RND_RANDOM, entropy, length); + return rc?CURLE_FAILED_INIT:CURLE_OK; +} + +static CURLcode gtls_sha256sum(const unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *sha256sum, /* output */ + size_t sha256len) +{ + struct sha256_ctx SHA256pw; + sha256_init(&SHA256pw); + sha256_update(&SHA256pw, (unsigned int)tmplen, tmp); + sha256_digest(&SHA256pw, (unsigned int)sha256len, sha256sum); + return CURLE_OK; +} + +static bool gtls_cert_status_request(void) +{ + return TRUE; +} + +static void *gtls_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) +{ + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + (void)info; + DEBUGASSERT(backend); + return backend->gtls.session; +} + +const struct Curl_ssl Curl_ssl_gnutls = { + { CURLSSLBACKEND_GNUTLS, "gnutls" }, /* info */ + + SSLSUPP_CA_PATH | + SSLSUPP_CERTINFO | + SSLSUPP_PINNEDPUBKEY | + SSLSUPP_HTTPS_PROXY, + + sizeof(struct gtls_ssl_backend_data), + + gtls_init, /* init */ + gtls_cleanup, /* cleanup */ + gtls_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + gtls_shutdown, /* shutdown */ + gtls_data_pending, /* data_pending */ + gtls_random, /* random */ + gtls_cert_status_request, /* cert_status_request */ + gtls_connect, /* connect */ + gtls_connect_nonblocking, /* connect_nonblocking */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ + gtls_get_internals, /* get_internals */ + gtls_close, /* close_one */ + Curl_none_close_all, /* close_all */ + gtls_session_free, /* session_free */ + Curl_none_set_engine, /* set_engine */ + Curl_none_set_engine_default, /* set_engine_default */ + Curl_none_engines_list, /* engines_list */ + Curl_none_false_start, /* false_start */ + gtls_sha256sum, /* sha256sum */ + NULL, /* associate_connection */ + NULL, /* disassociate_connection */ + NULL, /* free_multi_ssl_backend_data */ + gtls_recv, /* recv decrypted data */ + gtls_send, /* send data to encrypt */ +}; + +#endif /* USE_GNUTLS */ diff --git a/Utilities/cmcurl/lib/vtls/gtls.h b/Utilities/cmcurl/lib/vtls/gtls.h new file mode 100644 index 0000000..1a81c01 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/gtls.h @@ -0,0 +1,75 @@ +#ifndef HEADER_CURL_GTLS_H +#define HEADER_CURL_GTLS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include <curl/curl.h> + +#ifdef USE_GNUTLS + +#include <gnutls/gnutls.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 ssl_primary_config; +struct ssl_config_data; +struct ssl_peer; + +struct gtls_instance { + gnutls_session_t session; + gnutls_certificate_credentials_t cred; +#ifdef USE_GNUTLS_SRP + gnutls_srp_client_credentials_t srp_client_cred; +#endif +}; + +CURLcode +gtls_client_init(struct Curl_easy *data, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + struct ssl_peer *peer, + struct gtls_instance *gtls, + long *pverifyresult); + +CURLcode +Curl_gtls_verifyserver(struct Curl_easy *data, + gnutls_session_t session, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + struct ssl_peer *peer, + const char *pinned_key); + +extern const struct Curl_ssl Curl_ssl_gnutls; + +#endif /* USE_GNUTLS */ +#endif /* HEADER_CURL_GTLS_H */ diff --git a/Utilities/cmcurl/lib/vtls/hostcheck.c b/Utilities/cmcurl/lib/vtls/hostcheck.c new file mode 100644 index 0000000..2726dca --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/hostcheck.c @@ -0,0 +1,135 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_OPENSSL) \ + || defined(USE_SCHANNEL) +/* these backends use functions from this file */ + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN6_H +#include <netinet/in6.h> +#endif +#include "curl_memrchr.h" + +#include "hostcheck.h" +#include "strcase.h" +#include "hostip.h" + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/* check the two input strings with given length, but do not + assume they end in nul-bytes */ +static bool pmatch(const char *hostname, size_t hostlen, + const char *pattern, size_t patternlen) +{ + if(hostlen != patternlen) + return FALSE; + return strncasecompare(hostname, pattern, hostlen); +} + +/* + * Match a hostname against a wildcard pattern. + * E.g. + * "foo.host.com" matches "*.host.com". + * + * We use the matching rule described in RFC6125, section 6.4.3. + * https://datatracker.ietf.org/doc/html/rfc6125#section-6.4.3 + * + * In addition: ignore trailing dots in the host names and wildcards, so that + * the names are used normalized. This is what the browsers do. + * + * Do not allow wildcard matching on IP numbers. There are apparently + * certificates being used with an IP address in the CN field, thus making no + * apparent distinction between a name and an IP. We need to detect the use of + * an IP address and not wildcard match on such names. + * + * Only match on "*" being used for the leftmost label, not "a*", "a*b" nor + * "*b". + * + * Return TRUE on a match. FALSE if not. + * + * @unittest: 1397 + */ + +static bool hostmatch(const char *hostname, + size_t hostlen, + const char *pattern, + size_t patternlen) +{ + const char *pattern_label_end; + + DEBUGASSERT(pattern); + DEBUGASSERT(patternlen); + DEBUGASSERT(hostname); + DEBUGASSERT(hostlen); + + /* normalize pattern and hostname by stripping off trailing dots */ + if(hostname[hostlen-1]=='.') + hostlen--; + if(pattern[patternlen-1]=='.') + patternlen--; + + if(strncmp(pattern, "*.", 2)) + return pmatch(hostname, hostlen, pattern, patternlen); + + /* detect IP address as hostname and fail the match if so */ + else if(Curl_host_is_ipnum(hostname)) + return FALSE; + + /* We require at least 2 dots in the pattern to avoid too wide wildcard + match. */ + pattern_label_end = memchr(pattern, '.', patternlen); + if(!pattern_label_end || + (memrchr(pattern, '.', patternlen) == pattern_label_end)) + return pmatch(hostname, hostlen, pattern, patternlen); + else { + const char *hostname_label_end = memchr(hostname, '.', hostlen); + if(hostname_label_end) { + size_t skiphost = hostname_label_end - hostname; + size_t skiplen = pattern_label_end - pattern; + return pmatch(hostname_label_end, hostlen - skiphost, + pattern_label_end, patternlen - skiplen); + } + } + return FALSE; +} + +/* + * Curl_cert_hostcheck() returns TRUE if a match and FALSE if not. + */ +bool Curl_cert_hostcheck(const char *match, size_t matchlen, + const char *hostname, size_t hostlen) +{ + if(match && *match && hostname && *hostname) + return hostmatch(hostname, hostlen, match, matchlen); + return FALSE; +} + +#endif /* OPENSSL or SCHANNEL */ diff --git a/Utilities/cmcurl/lib/vtls/hostcheck.h b/Utilities/cmcurl/lib/vtls/hostcheck.h new file mode 100644 index 0000000..22a1ac2 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/hostcheck.h @@ -0,0 +1,33 @@ +#ifndef HEADER_CURL_HOSTCHECK_H +#define HEADER_CURL_HOSTCHECK_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include <curl/curl.h> + +/* returns TRUE if there's a match */ +bool Curl_cert_hostcheck(const char *match_pattern, size_t matchlen, + const char *hostname, size_t hostlen); + +#endif /* HEADER_CURL_HOSTCHECK_H */ diff --git a/Utilities/cmcurl/lib/vtls/keylog.c b/Utilities/cmcurl/lib/vtls/keylog.c new file mode 100644 index 0000000..fbcb25c --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/keylog.c @@ -0,0 +1,166 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if defined(USE_OPENSSL) || \ + defined(USE_WOLFSSL) || \ + (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \ + defined(USE_QUICHE) + +#include "keylog.h" +#include <curl/curl.h> + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +#define KEYLOG_LABEL_MAXLEN (sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET") - 1) + +#define CLIENT_RANDOM_SIZE 32 + +/* + * The master secret in TLS 1.2 and before is always 48 bytes. In TLS 1.3, the + * secret size depends on the cipher suite's hash function which is 32 bytes + * for SHA-256 and 48 bytes for SHA-384. + */ +#define SECRET_MAXLEN 48 + + +/* The fp for the open SSLKEYLOGFILE, or NULL if not open */ +static FILE *keylog_file_fp; + +void +Curl_tls_keylog_open(void) +{ + char *keylog_file_name; + + if(!keylog_file_fp) { + keylog_file_name = curl_getenv("SSLKEYLOGFILE"); + if(keylog_file_name) { + keylog_file_fp = fopen(keylog_file_name, FOPEN_APPENDTEXT); + if(keylog_file_fp) { +#ifdef _WIN32 + if(setvbuf(keylog_file_fp, NULL, _IONBF, 0)) +#else + if(setvbuf(keylog_file_fp, NULL, _IOLBF, 4096)) +#endif + { + fclose(keylog_file_fp); + keylog_file_fp = NULL; + } + } + Curl_safefree(keylog_file_name); + } + } +} + +void +Curl_tls_keylog_close(void) +{ + if(keylog_file_fp) { + fclose(keylog_file_fp); + keylog_file_fp = NULL; + } +} + +bool +Curl_tls_keylog_enabled(void) +{ + return keylog_file_fp != NULL; +} + +bool +Curl_tls_keylog_write_line(const char *line) +{ + /* The current maximum valid keylog line length LF and NUL is 195. */ + size_t linelen; + char buf[256]; + + if(!keylog_file_fp || !line) { + return false; + } + + linelen = strlen(line); + if(linelen == 0 || linelen > sizeof(buf) - 2) { + /* Empty line or too big to fit in a LF and NUL. */ + return false; + } + + memcpy(buf, line, linelen); + if(line[linelen - 1] != '\n') { + buf[linelen++] = '\n'; + } + buf[linelen] = '\0'; + + /* Using fputs here instead of fprintf since libcurl's fprintf replacement + may not be thread-safe. */ + fputs(buf, keylog_file_fp); + 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) +{ + const char *hex = "0123456789ABCDEF"; + size_t pos, i; + char line[KEYLOG_LABEL_MAXLEN + 1 + 2 * CLIENT_RANDOM_SIZE + 1 + + 2 * SECRET_MAXLEN + 1 + 1]; + + if(!keylog_file_fp) { + return false; + } + + pos = strlen(label); + if(pos > KEYLOG_LABEL_MAXLEN || !secretlen || secretlen > SECRET_MAXLEN) { + /* Should never happen - sanity check anyway. */ + return false; + } + + memcpy(line, label, pos); + line[pos++] = ' '; + + /* Client Random */ + for(i = 0; i < CLIENT_RANDOM_SIZE; i++) { + line[pos++] = hex[client_random[i] >> 4]; + line[pos++] = hex[client_random[i] & 0xF]; + } + line[pos++] = ' '; + + /* Secret */ + for(i = 0; i < secretlen; i++) { + line[pos++] = hex[secret[i] >> 4]; + line[pos++] = hex[secret[i] & 0xF]; + } + line[pos++] = '\n'; + line[pos] = '\0'; + + /* Using fputs here instead of fprintf since libcurl's fprintf replacement + may not be thread-safe. */ + fputs(line, keylog_file_fp); + return true; +} + +#endif /* TLS or QUIC backend */ diff --git a/Utilities/cmcurl/lib/vtls/keylog.h b/Utilities/cmcurl/lib/vtls/keylog.h new file mode 100644 index 0000000..eff5bf3 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/keylog.h @@ -0,0 +1,58 @@ +#ifndef HEADER_CURL_KEYLOG_H +#define HEADER_CURL_KEYLOG_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +/* + * Opens the TLS key log file if requested by the user. The SSLKEYLOGFILE + * environment variable specifies the output file. + */ +void Curl_tls_keylog_open(void); + +/* + * Closes the TLS key log file if not already. + */ +void Curl_tls_keylog_close(void); + +/* + * Returns true if the user successfully enabled the TLS key log file. + */ +bool Curl_tls_keylog_enabled(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[32], + const unsigned char *secret, size_t secretlen); + +/* + * Appends a line to the key log file, ensure it is terminated by a LF. + * Returns true iff the key log file is open and a valid line was provided. + */ +bool Curl_tls_keylog_write_line(const char *line); + +#endif /* HEADER_CURL_KEYLOG_H */ diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.c b/Utilities/cmcurl/lib/vtls/mbedtls.c new file mode 100644 index 0000000..38f7de7 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/mbedtls.c @@ -0,0 +1,1294 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com> + * + * 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 + * + ***************************************************************************/ + +/* + * Source file for all mbedTLS-specific code for the TLS/SSL layer. No code + * but vtls.c should ever call or use these functions. + * + */ + +#include "curl_setup.h" + +#ifdef USE_MBEDTLS + +/* Define this to enable lots of debugging for mbedTLS */ +/* #define MBEDTLS_DEBUG */ + +#include <mbedtls/version.h> +#if MBEDTLS_VERSION_NUMBER >= 0x02040000 +#include <mbedtls/net_sockets.h> +#else +#include <mbedtls/net.h> +#endif +#include <mbedtls/ssl.h> +#include <mbedtls/x509.h> + +#include <mbedtls/error.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/sha256.h> + +#if MBEDTLS_VERSION_MAJOR >= 2 +# ifdef MBEDTLS_DEBUG +# include <mbedtls/debug.h> +# endif +#endif + +#include "urldata.h" +#include "sendf.h" +#include "inet_pton.h" +#include "mbedtls.h" +#include "vtls.h" +#include "vtls_int.h" +#include "parsedate.h" +#include "connect.h" /* for the connect timeout */ +#include "select.h" +#include "multiif.h" +#include "mbedtls_threadlock.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* ALPN for http2 */ +#ifdef USE_HTTP2 +# undef HAS_ALPN +# ifdef MBEDTLS_SSL_ALPN +# define HAS_ALPN +# endif +#endif + +struct mbed_ssl_backend_data { + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_entropy_context entropy; + mbedtls_ssl_context ssl; + mbedtls_x509_crt cacert; + mbedtls_x509_crt clicert; +#ifdef MBEDTLS_X509_CRL_PARSE_C + mbedtls_x509_crl crl; +#endif + mbedtls_pk_context pk; + mbedtls_ssl_config config; +#ifdef HAS_ALPN + const char *protocols[3]; +#endif +}; + +/* apply threading? */ +#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) +#define THREADING_SUPPORT +#endif + +#ifndef MBEDTLS_ERROR_C +#define mbedtls_strerror(a,b,c) b[0] = 0 +#endif + +#if defined(THREADING_SUPPORT) +static mbedtls_entropy_context ts_entropy; + +static int entropy_init_initialized = 0; + +/* start of entropy_init_mutex() */ +static void entropy_init_mutex(mbedtls_entropy_context *ctx) +{ + /* lock 0 = entropy_init_mutex() */ + Curl_mbedtlsthreadlock_lock_function(0); + if(entropy_init_initialized == 0) { + mbedtls_entropy_init(ctx); + entropy_init_initialized = 1; + } + Curl_mbedtlsthreadlock_unlock_function(0); +} +/* end of entropy_init_mutex() */ + +/* start of entropy_func_mutex() */ +static int entropy_func_mutex(void *data, unsigned char *output, size_t len) +{ + int ret; + /* lock 1 = entropy_func_mutex() */ + Curl_mbedtlsthreadlock_lock_function(1); + ret = mbedtls_entropy_func(data, output, len); + Curl_mbedtlsthreadlock_unlock_function(1); + + return ret; +} +/* end of entropy_func_mutex() */ + +#endif /* THREADING_SUPPORT */ + +#ifdef MBEDTLS_DEBUG +static void mbed_debug(void *context, int level, const char *f_name, + int line_nb, const char *line) +{ + struct Curl_easy *data = NULL; + + if(!context) + return; + + data = (struct Curl_easy *)context; + + infof(data, "%s", line); + (void) level; +} +#else +#endif + +static int mbedtls_bio_cf_write(void *bio, + const unsigned char *buf, size_t blen) +{ + struct Curl_cfilter *cf = bio; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nwritten; + CURLcode result; + + DEBUGASSERT(data); + nwritten = Curl_conn_cf_send(cf->next, data, (char *)buf, blen, &result); + CURL_TRC_CF(data, cf, "mbedtls_bio_cf_out_write(len=%zu) -> %zd, err=%d", + blen, nwritten, result); + if(nwritten < 0 && CURLE_AGAIN == result) { + nwritten = MBEDTLS_ERR_SSL_WANT_WRITE; + } + return (int)nwritten; +} + +static int mbedtls_bio_cf_read(void *bio, unsigned char *buf, size_t blen) +{ + struct Curl_cfilter *cf = bio; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nread; + CURLcode result; + + DEBUGASSERT(data); + /* OpenSSL catches this case, so should we. */ + if(!buf) + return 0; + + nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, blen, &result); + CURL_TRC_CF(data, cf, "mbedtls_bio_cf_in_read(len=%zu) -> %zd, err=%d", + blen, nread, result); + if(nread < 0 && CURLE_AGAIN == result) { + nread = MBEDTLS_ERR_SSL_WANT_READ; + } + return (int)nread; +} + +/* + * profile + */ +static const mbedtls_x509_crt_profile mbedtls_x509_crt_profile_fr = +{ + /* Hashes from SHA-1 and above */ + MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA1) | + MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_RIPEMD160) | + MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA224) | + MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA256) | + MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA384) | + MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA512), + 0xFFFFFFF, /* Any PK alg */ + 0xFFFFFFF, /* Any curve */ + 1024, /* RSA min key len */ +}; + +/* See https://tls.mbed.org/discussions/generic/ + howto-determine-exact-buffer-len-for-mbedtls_pk_write_pubkey_der +*/ +#define RSA_PUB_DER_MAX_BYTES (38 + 2 * MBEDTLS_MPI_MAX_SIZE) +#define ECP_PUB_DER_MAX_BYTES (30 + 2 * MBEDTLS_ECP_MAX_BYTES) + +#define PUB_DER_MAX_BYTES (RSA_PUB_DER_MAX_BYTES > ECP_PUB_DER_MAX_BYTES ? \ + RSA_PUB_DER_MAX_BYTES : ECP_PUB_DER_MAX_BYTES) + +static CURLcode mbedtls_version_from_curl(int *mbedver, long version) +{ +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + switch(version) { + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + *mbedver = MBEDTLS_SSL_MINOR_VERSION_3; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_3: + break; + } +#else + switch(version) { + case CURL_SSLVERSION_TLSv1_0: + *mbedver = MBEDTLS_SSL_MINOR_VERSION_1; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_1: + *mbedver = MBEDTLS_SSL_MINOR_VERSION_2; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_2: + *mbedver = MBEDTLS_SSL_MINOR_VERSION_3; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_3: + break; + } +#endif + + return CURLE_SSL_CONNECT_ERROR; +} + +static CURLcode +set_ssl_version_min_max(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + int mbedtls_ver_min = MBEDTLS_SSL_MINOR_VERSION_3; + int mbedtls_ver_max = MBEDTLS_SSL_MINOR_VERSION_3; +#else + int mbedtls_ver_min = MBEDTLS_SSL_MINOR_VERSION_1; + int mbedtls_ver_max = MBEDTLS_SSL_MINOR_VERSION_1; +#endif + long ssl_version = conn_config->version; + long ssl_version_max = conn_config->version_max; + CURLcode result = CURLE_OK; + + DEBUGASSERT(backend); + + switch(ssl_version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + ssl_version = CURL_SSLVERSION_TLSv1_0; + break; + } + + switch(ssl_version_max) { + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_DEFAULT: + ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; + break; + } + + result = mbedtls_version_from_curl(&mbedtls_ver_min, ssl_version); + if(result) { + failf(data, "unsupported min version passed via CURLOPT_SSLVERSION"); + return result; + } + result = mbedtls_version_from_curl(&mbedtls_ver_max, ssl_version_max >> 16); + if(result) { + failf(data, "unsupported max version passed via CURLOPT_SSLVERSION"); + return result; + } + + mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, + mbedtls_ver_min); + mbedtls_ssl_conf_max_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, + mbedtls_ver_max); + + return result; +} + +static CURLcode +mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + const char * const ssl_cafile = + /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ + (ca_info_blob ? NULL : conn_config->CAfile); + const bool verifypeer = conn_config->verifypeer; + const char * const ssl_capath = conn_config->CApath; + char * const ssl_cert = ssl_config->primary.clientcert; + const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; + const char * const ssl_crlfile = ssl_config->primary.CRLfile; + const char *hostname = connssl->peer.hostname; + int ret = -1; + char errorbuf[128]; + + DEBUGASSERT(backend); + + if((conn_config->version == CURL_SSLVERSION_SSLv2) || + (conn_config->version == CURL_SSLVERSION_SSLv3)) { + failf(data, "Not supported SSL version"); + return CURLE_NOT_BUILT_IN; + } + +#ifdef THREADING_SUPPORT + entropy_init_mutex(&ts_entropy); + mbedtls_ctr_drbg_init(&backend->ctr_drbg); + + ret = mbedtls_ctr_drbg_seed(&backend->ctr_drbg, entropy_func_mutex, + &ts_entropy, NULL, 0); + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s", + -ret, errorbuf); + return CURLE_FAILED_INIT; + } +#else + mbedtls_entropy_init(&backend->entropy); + mbedtls_ctr_drbg_init(&backend->ctr_drbg); + + ret = mbedtls_ctr_drbg_seed(&backend->ctr_drbg, mbedtls_entropy_func, + &backend->entropy, NULL, 0); + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s", + -ret, errorbuf); + return CURLE_FAILED_INIT; + } +#endif /* THREADING_SUPPORT */ + + /* Load the trusted CA */ + mbedtls_x509_crt_init(&backend->cacert); + + if(ca_info_blob && verifypeer) { + /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null + terminated even when provided the exact length, forcing us to waste + extra memory here. */ + unsigned char *newblob = malloc(ca_info_blob->len + 1); + if(!newblob) + return CURLE_OUT_OF_MEMORY; + memcpy(newblob, ca_info_blob->data, ca_info_blob->len); + newblob[ca_info_blob->len] = 0; /* null terminate */ + ret = mbedtls_x509_crt_parse(&backend->cacert, newblob, + ca_info_blob->len + 1); + free(newblob); + if(ret<0) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "Error importing ca cert blob - mbedTLS: (-0x%04X) %s", + -ret, errorbuf); + return CURLE_SSL_CERTPROBLEM; + } + } + + if(ssl_cafile && verifypeer) { +#ifdef MBEDTLS_FS_IO + ret = mbedtls_x509_crt_parse_file(&backend->cacert, ssl_cafile); + + if(ret<0) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "Error reading ca cert file %s - mbedTLS: (-0x%04X) %s", + ssl_cafile, -ret, errorbuf); + return CURLE_SSL_CACERT_BADFILE; + } +#else + failf(data, "mbedtls: functions that use the filesystem not built in"); + return CURLE_NOT_BUILT_IN; +#endif + } + + if(ssl_capath) { +#ifdef MBEDTLS_FS_IO + ret = mbedtls_x509_crt_parse_path(&backend->cacert, ssl_capath); + + if(ret<0) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "Error reading ca cert path %s - mbedTLS: (-0x%04X) %s", + ssl_capath, -ret, errorbuf); + + if(verifypeer) + return CURLE_SSL_CACERT_BADFILE; + } +#else + failf(data, "mbedtls: functions that use the filesystem not built in"); + return CURLE_NOT_BUILT_IN; +#endif + } + + /* Load the client certificate */ + mbedtls_x509_crt_init(&backend->clicert); + + if(ssl_cert) { +#ifdef MBEDTLS_FS_IO + ret = mbedtls_x509_crt_parse_file(&backend->clicert, ssl_cert); + + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "Error reading client cert file %s - mbedTLS: (-0x%04X) %s", + ssl_cert, -ret, errorbuf); + + return CURLE_SSL_CERTPROBLEM; + } +#else + failf(data, "mbedtls: functions that use the filesystem not built in"); + return CURLE_NOT_BUILT_IN; +#endif + } + + if(ssl_cert_blob) { + /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null + terminated even when provided the exact length, forcing us to waste + extra memory here. */ + unsigned char *newblob = malloc(ssl_cert_blob->len + 1); + if(!newblob) + return CURLE_OUT_OF_MEMORY; + memcpy(newblob, ssl_cert_blob->data, ssl_cert_blob->len); + newblob[ssl_cert_blob->len] = 0; /* null terminate */ + ret = mbedtls_x509_crt_parse(&backend->clicert, newblob, + ssl_cert_blob->len + 1); + free(newblob); + + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "Error reading private key %s - mbedTLS: (-0x%04X) %s", + ssl_config->key, -ret, errorbuf); + return CURLE_SSL_CERTPROBLEM; + } + } + + /* Load the client private key */ + mbedtls_pk_init(&backend->pk); + + if(ssl_config->key || ssl_config->key_blob) { + if(ssl_config->key) { +#ifdef MBEDTLS_FS_IO +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key, + ssl_config->key_passwd, + mbedtls_ctr_drbg_random, + &backend->ctr_drbg); +#else + ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key, + ssl_config->key_passwd); +#endif + + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "Error reading private key %s - mbedTLS: (-0x%04X) %s", + ssl_config->key, -ret, errorbuf); + return CURLE_SSL_CERTPROBLEM; + } +#else + failf(data, "mbedtls: functions that use the filesystem not built in"); + return CURLE_NOT_BUILT_IN; +#endif + } + else { + const struct curl_blob *ssl_key_blob = ssl_config->key_blob; + const unsigned char *key_data = + (const unsigned char *)ssl_key_blob->data; + const char *passwd = ssl_config->key_passwd; +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret = mbedtls_pk_parse_key(&backend->pk, key_data, ssl_key_blob->len, + (const unsigned char *)passwd, + passwd ? strlen(passwd) : 0, + mbedtls_ctr_drbg_random, + &backend->ctr_drbg); +#else + ret = mbedtls_pk_parse_key(&backend->pk, key_data, ssl_key_blob->len, + (const unsigned char *)passwd, + passwd ? strlen(passwd) : 0); +#endif + + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "Error parsing private key - mbedTLS: (-0x%04X) %s", + -ret, errorbuf); + return CURLE_SSL_CERTPROBLEM; + } + } + + if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || + mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_ECKEY))) + ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; + } + + /* Load the CRL */ +#ifdef MBEDTLS_X509_CRL_PARSE_C + mbedtls_x509_crl_init(&backend->crl); + + if(ssl_crlfile) { +#ifdef MBEDTLS_FS_IO + ret = mbedtls_x509_crl_parse_file(&backend->crl, ssl_crlfile); + + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "Error reading CRL file %s - mbedTLS: (-0x%04X) %s", + ssl_crlfile, -ret, errorbuf); + + return CURLE_SSL_CRL_BADFILE; + } +#else + failf(data, "mbedtls: functions that use the filesystem not built in"); + return CURLE_NOT_BUILT_IN; +#endif + } +#else + if(ssl_crlfile) { + failf(data, "mbedtls: crl support not built in"); + return CURLE_NOT_BUILT_IN; + } +#endif + + infof(data, "mbedTLS: Connecting to %s:%d", hostname, connssl->port); + + mbedtls_ssl_config_init(&backend->config); + ret = mbedtls_ssl_config_defaults(&backend->config, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if(ret) { + failf(data, "mbedTLS: ssl_config failed"); + return CURLE_SSL_CONNECT_ERROR; + } + + mbedtls_ssl_init(&backend->ssl); + if(mbedtls_ssl_setup(&backend->ssl, &backend->config)) { + failf(data, "mbedTLS: ssl_init failed"); + return CURLE_SSL_CONNECT_ERROR; + } + + /* new profile with RSA min key len = 1024 ... */ + mbedtls_ssl_conf_cert_profile(&backend->config, + &mbedtls_x509_crt_profile_fr); + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: +#if MBEDTLS_VERSION_NUMBER < 0x03000000 + mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, + MBEDTLS_SSL_MINOR_VERSION_1); + infof(data, "mbedTLS: Set min SSL version to TLS 1.0"); + break; +#endif + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + case CURL_SSLVERSION_TLSv1_3: + { + CURLcode result = set_ssl_version_min_max(cf, data); + if(result != CURLE_OK) + return result; + break; + } + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + + mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_OPTIONAL); + + mbedtls_ssl_conf_rng(&backend->config, mbedtls_ctr_drbg_random, + &backend->ctr_drbg); + mbedtls_ssl_set_bio(&backend->ssl, cf, + mbedtls_bio_cf_write, + mbedtls_bio_cf_read, + NULL /* rev_timeout() */); + + mbedtls_ssl_conf_ciphersuites(&backend->config, + mbedtls_ssl_list_ciphersuites()); + +#if defined(MBEDTLS_SSL_RENEGOTIATION) + mbedtls_ssl_conf_renegotiation(&backend->config, + MBEDTLS_SSL_RENEGOTIATION_ENABLED); +#endif + +#if defined(MBEDTLS_SSL_SESSION_TICKETS) + mbedtls_ssl_conf_session_tickets(&backend->config, + MBEDTLS_SSL_SESSION_TICKETS_DISABLED); +#endif + + /* Check if there's a cached ID we can/should use here! */ + if(ssl_config->primary.sessionid) { + void *old_session = NULL; + + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, &old_session, NULL)) { + ret = mbedtls_ssl_set_session(&backend->ssl, old_session); + if(ret) { + Curl_ssl_sessionid_unlock(data); + failf(data, "mbedtls_ssl_set_session returned -0x%x", -ret); + return CURLE_SSL_CONNECT_ERROR; + } + infof(data, "mbedTLS reusing session"); + } + Curl_ssl_sessionid_unlock(data); + } + + mbedtls_ssl_conf_ca_chain(&backend->config, + &backend->cacert, +#ifdef MBEDTLS_X509_CRL_PARSE_C + &backend->crl); +#else + NULL); +#endif + + if(ssl_config->key || ssl_config->key_blob) { + mbedtls_ssl_conf_own_cert(&backend->config, + &backend->clicert, &backend->pk); + } + + if(connssl->peer.sni) { + if(mbedtls_ssl_set_hostname(&backend->ssl, connssl->peer.sni)) { + /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and + the name to set in the SNI extension. So even if curl connects to a + host specified as an IP address, this function must be used. */ + failf(data, "Failed to set SNI"); + return CURLE_SSL_CONNECT_ERROR; + } + } + +#ifdef HAS_ALPN + if(connssl->alpn) { + struct alpn_proto_buf proto; + size_t i; + + for(i = 0; i < connssl->alpn->count; ++i) { + backend->protocols[i] = connssl->alpn->entries[i]; + } + /* this function doesn't clone the protocols array, which is why we need + to keep it around */ + if(mbedtls_ssl_conf_alpn_protocols(&backend->config, + &backend->protocols[0])) { + failf(data, "Failed setting ALPN protocols"); + return CURLE_SSL_CONNECT_ERROR; + } + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } +#endif + +#ifdef MBEDTLS_DEBUG + /* In order to make that work in mbedtls MBEDTLS_DEBUG_C must be defined. */ + mbedtls_ssl_conf_dbg(&backend->config, mbed_debug, data); + /* - 0 No debug + * - 1 Error + * - 2 State change + * - 3 Informational + * - 4 Verbose + */ + mbedtls_debug_set_threshold(4); +#endif + + /* give application a chance to interfere with mbedTLS set up. */ + if(data->set.ssl.fsslctx) { + ret = (*data->set.ssl.fsslctx)(data, &backend->config, + data->set.ssl.fsslctxp); + if(ret) { + failf(data, "error signaled by ssl ctx callback"); + return ret; + } + } + + connssl->connecting_state = ssl_connect_2; + + return CURLE_OK; +} + +static CURLcode +mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + int ret; + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + const mbedtls_x509_crt *peercert; + const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf)? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + + DEBUGASSERT(backend); + + ret = mbedtls_ssl_handshake(&backend->ssl); + + if(ret == MBEDTLS_ERR_SSL_WANT_READ) { + connssl->connecting_state = ssl_connect_2_reading; + return CURLE_OK; + } + else if(ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + connssl->connecting_state = ssl_connect_2_writing; + return CURLE_OK; + } + else if(ret) { + char errorbuf[128]; + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "ssl_handshake returned - mbedTLS: (-0x%04X) %s", + -ret, errorbuf); + return CURLE_SSL_CONNECT_ERROR; + } + + infof(data, "mbedTLS: Handshake complete, cipher is %s", + mbedtls_ssl_get_ciphersuite(&backend->ssl)); + + ret = mbedtls_ssl_get_verify_result(&backend->ssl); + + if(!conn_config->verifyhost) + /* Ignore hostname errors if verifyhost is disabled */ + ret &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH; + + if(ret && conn_config->verifypeer) { + if(ret & MBEDTLS_X509_BADCERT_EXPIRED) + failf(data, "Cert verify failed: BADCERT_EXPIRED"); + + else if(ret & MBEDTLS_X509_BADCERT_REVOKED) + failf(data, "Cert verify failed: BADCERT_REVOKED"); + + else if(ret & MBEDTLS_X509_BADCERT_CN_MISMATCH) + failf(data, "Cert verify failed: BADCERT_CN_MISMATCH"); + + else if(ret & MBEDTLS_X509_BADCERT_NOT_TRUSTED) + failf(data, "Cert verify failed: BADCERT_NOT_TRUSTED"); + + else if(ret & MBEDTLS_X509_BADCERT_FUTURE) + failf(data, "Cert verify failed: BADCERT_FUTURE"); + + return CURLE_PEER_FAILED_VERIFICATION; + } + + peercert = mbedtls_ssl_get_peer_cert(&backend->ssl); + + if(peercert && data->set.verbose) { + const size_t bufsize = 16384; + char *buffer = malloc(bufsize); + + if(!buffer) + return CURLE_OUT_OF_MEMORY; + + if(mbedtls_x509_crt_info(buffer, bufsize, "* ", peercert) > 0) + infof(data, "Dumping cert info: %s", buffer); + else + infof(data, "Unable to dump certificate information"); + + free(buffer); + } + + if(pinnedpubkey) { + int size; + CURLcode result; + mbedtls_x509_crt *p = NULL; + unsigned char *pubkey = NULL; + +#if MBEDTLS_VERSION_NUMBER == 0x03000000 + if(!peercert || !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p) || + !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len)) { +#else + if(!peercert || !peercert->raw.p || !peercert->raw.len) { +#endif + failf(data, "Failed due to missing peer certificate"); + return CURLE_SSL_PINNEDPUBKEYNOTMATCH; + } + + p = calloc(1, sizeof(*p)); + + if(!p) + return CURLE_OUT_OF_MEMORY; + + pubkey = malloc(PUB_DER_MAX_BYTES); + + if(!pubkey) { + result = CURLE_OUT_OF_MEMORY; + goto pinnedpubkey_error; + } + + mbedtls_x509_crt_init(p); + + /* Make a copy of our const peercert because mbedtls_pk_write_pubkey_der + needs a non-const key, for now. + https://github.com/ARMmbed/mbedtls/issues/396 */ +#if MBEDTLS_VERSION_NUMBER == 0x03000000 + if(mbedtls_x509_crt_parse_der(p, + peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p), + peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len))) { +#else + if(mbedtls_x509_crt_parse_der(p, peercert->raw.p, peercert->raw.len)) { +#endif + failf(data, "Failed copying peer certificate"); + result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; + goto pinnedpubkey_error; + } + +#if MBEDTLS_VERSION_NUMBER == 0x03000000 + size = mbedtls_pk_write_pubkey_der(&p->MBEDTLS_PRIVATE(pk), pubkey, + PUB_DER_MAX_BYTES); +#else + size = mbedtls_pk_write_pubkey_der(&p->pk, pubkey, PUB_DER_MAX_BYTES); +#endif + + if(size <= 0) { + failf(data, "Failed copying public key from peer certificate"); + result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; + goto pinnedpubkey_error; + } + + /* mbedtls_pk_write_pubkey_der writes data at the end of the buffer. */ + result = Curl_pin_peer_pubkey(data, + pinnedpubkey, + &pubkey[PUB_DER_MAX_BYTES - size], size); +pinnedpubkey_error: + mbedtls_x509_crt_free(p); + free(p); + free(pubkey); + if(result) { + return result; + } + } + +#ifdef HAS_ALPN + if(connssl->alpn) { + const char *proto = mbedtls_ssl_get_alpn_protocol(&backend->ssl); + + Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto, + proto? strlen(proto) : 0); + } +#endif + + connssl->connecting_state = ssl_connect_3; + infof(data, "SSL connected"); + + return CURLE_OK; +} + +static CURLcode +mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + CURLcode retcode = CURLE_OK; + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + + DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + DEBUGASSERT(backend); + + if(ssl_config->primary.sessionid) { + int ret; + mbedtls_ssl_session *our_ssl_sessionid; + void *old_ssl_sessionid = NULL; + bool added = FALSE; + + our_ssl_sessionid = malloc(sizeof(mbedtls_ssl_session)); + if(!our_ssl_sessionid) + return CURLE_OUT_OF_MEMORY; + + mbedtls_ssl_session_init(our_ssl_sessionid); + + ret = mbedtls_ssl_get_session(&backend->ssl, our_ssl_sessionid); + if(ret) { + if(ret != MBEDTLS_ERR_SSL_ALLOC_FAILED) + mbedtls_ssl_session_free(our_ssl_sessionid); + free(our_ssl_sessionid); + failf(data, "mbedtls_ssl_get_session returned -0x%x", -ret); + return CURLE_SSL_CONNECT_ERROR; + } + + /* If there's already a matching session in the cache, delete it */ + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)) + Curl_ssl_delsessionid(data, old_ssl_sessionid); + + retcode = Curl_ssl_addsessionid(cf, data, our_ssl_sessionid, + 0, &added); + Curl_ssl_sessionid_unlock(data); + if(!added) { + mbedtls_ssl_session_free(our_ssl_sessionid); + free(our_ssl_sessionid); + } + if(retcode) { + failf(data, "failed to store ssl session"); + return retcode; + } + } + + connssl->connecting_state = ssl_connect_done; + + return CURLE_OK; +} + +static ssize_t mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *mem, size_t len, + CURLcode *curlcode) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + int ret = -1; + + (void)data; + DEBUGASSERT(backend); + ret = mbedtls_ssl_write(&backend->ssl, (unsigned char *)mem, len); + + if(ret < 0) { + *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_WRITE) ? + CURLE_AGAIN : CURLE_SEND_ERROR; + ret = -1; + } + + return ret; +} + +static void mbedtls_close_all(struct Curl_easy *data) +{ + (void)data; +} + +static void mbedtls_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + char buf[32]; + + (void)data; + DEBUGASSERT(backend); + + /* Maybe the server has already sent a close notify alert. + Read it to avoid an RST on the TCP connection. */ + (void)mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf, sizeof(buf)); + + mbedtls_pk_free(&backend->pk); + mbedtls_x509_crt_free(&backend->clicert); + mbedtls_x509_crt_free(&backend->cacert); +#ifdef MBEDTLS_X509_CRL_PARSE_C + mbedtls_x509_crl_free(&backend->crl); +#endif + mbedtls_ssl_config_free(&backend->config); + mbedtls_ssl_free(&backend->ssl); + mbedtls_ctr_drbg_free(&backend->ctr_drbg); +#ifndef THREADING_SUPPORT + mbedtls_entropy_free(&backend->entropy); +#endif /* THREADING_SUPPORT */ +} + +static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t buffersize, + CURLcode *curlcode) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + int ret = -1; + ssize_t len = -1; + + (void)data; + DEBUGASSERT(backend); + + ret = mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf, + buffersize); + + if(ret <= 0) { + if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) + return 0; + + *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_READ) ? + CURLE_AGAIN : CURLE_RECV_ERROR; + return -1; + } + + len = ret; + + return len; +} + +static void mbedtls_session_free(void *ptr) +{ + mbedtls_ssl_session_free(ptr); + free(ptr); +} + +static size_t mbedtls_version(char *buffer, size_t size) +{ +#ifdef MBEDTLS_VERSION_C + /* if mbedtls_version_get_number() is available it is better */ + unsigned int version = mbedtls_version_get_number(); + return msnprintf(buffer, size, "mbedTLS/%u.%u.%u", version>>24, + (version>>16)&0xff, (version>>8)&0xff); +#else + return msnprintf(buffer, size, "mbedTLS/%s", MBEDTLS_VERSION_STRING); +#endif +} + +static CURLcode mbedtls_random(struct Curl_easy *data, + unsigned char *entropy, size_t length) +{ +#if defined(MBEDTLS_CTR_DRBG_C) + int ret = -1; + char errorbuf[128]; + mbedtls_entropy_context ctr_entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_entropy_init(&ctr_entropy); + mbedtls_ctr_drbg_init(&ctr_drbg); + + ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, + &ctr_entropy, NULL, 0); + + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s", + -ret, errorbuf); + } + else { + ret = mbedtls_ctr_drbg_random(&ctr_drbg, entropy, length); + + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "mbedtls_ctr_drbg_random returned (-0x%04X) %s", + -ret, errorbuf); + } + } + + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&ctr_entropy); + + return ret == 0 ? CURLE_OK : CURLE_FAILED_INIT; +#elif defined(MBEDTLS_HAVEGE_C) + mbedtls_havege_state hs; + mbedtls_havege_init(&hs); + mbedtls_havege_random(&hs, entropy, length); + mbedtls_havege_free(&hs); + return CURLE_OK; +#else + return CURLE_NOT_BUILT_IN; +#endif +} + +static CURLcode +mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, + bool nonblocking, + bool *done) +{ + CURLcode retcode; + struct ssl_connect_data *connssl = cf->ctx; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + timediff_t timeout_ms; + int what; + + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + if(ssl_connect_1 == connssl->connecting_state) { + /* Find out how much more time we're allowed */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + retcode = mbed_connect_step1(cf, data); + if(retcode) + return retcode; + } + + while(ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + + /* check allowed time left */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + if(connssl->connecting_state == ssl_connect_2_reading + || connssl->connecting_state == ssl_connect_2_writing) { + + curl_socket_t writefd = ssl_connect_2_writing == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, + nonblocking ? 0 : timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) { + *done = FALSE; + return CURLE_OK; + } + else { + /* timeout */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + /* Run transaction, and return to the caller if it failed or if + * this connection is part of a multi handle and this loop would + * execute again. This permits the owner of a multi handle to + * abort a connection attempt before step2 has completed while + * ensuring that a client using select() or epoll() will always + * have a valid fdset to wait on. + */ + retcode = mbed_connect_step2(cf, data); + if(retcode || (nonblocking && + (ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state))) + return retcode; + + } /* repeat step2 until all transactions are done. */ + + if(ssl_connect_3 == connssl->connecting_state) { + retcode = mbed_connect_step3(cf, data); + if(retcode) + return retcode; + } + + if(ssl_connect_done == connssl->connecting_state) { + connssl->state = ssl_connection_complete; + *done = TRUE; + } + else + *done = FALSE; + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + + return CURLE_OK; +} + +static CURLcode mbedtls_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + return mbed_connect_common(cf, data, TRUE, done); +} + + +static CURLcode mbedtls_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode retcode; + bool done = FALSE; + + retcode = mbed_connect_common(cf, data, FALSE, &done); + if(retcode) + return retcode; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +/* + * return 0 error initializing SSL + * return 1 SSL initialized successfully + */ +static int mbedtls_init(void) +{ + return Curl_mbedtlsthreadlock_thread_setup(); +} + +static void mbedtls_cleanup(void) +{ + (void)Curl_mbedtlsthreadlock_thread_cleanup(); +} + +static bool mbedtls_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct ssl_connect_data *ctx = cf->ctx; + struct mbed_ssl_backend_data *backend; + + (void)data; + DEBUGASSERT(ctx && ctx->backend); + backend = (struct mbed_ssl_backend_data *)ctx->backend; + return mbedtls_ssl_get_bytes_avail(&backend->ssl) != 0; +} + +static CURLcode mbedtls_sha256sum(const unsigned char *input, + size_t inputlen, + unsigned char *sha256sum, + size_t sha256len UNUSED_PARAM) +{ + /* TODO: explain this for different mbedtls 2.x vs 3 version */ + (void)sha256len; +#if MBEDTLS_VERSION_NUMBER < 0x02070000 + mbedtls_sha256(input, inputlen, sha256sum, 0); +#else + /* returns 0 on success, otherwise failure */ +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + if(mbedtls_sha256(input, inputlen, sha256sum, 0) != 0) +#else + if(mbedtls_sha256_ret(input, inputlen, sha256sum, 0) != 0) +#endif + return CURLE_BAD_FUNCTION_ARGUMENT; +#endif + return CURLE_OK; +} + +static void *mbedtls_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) +{ + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + (void)info; + DEBUGASSERT(backend); + return &backend->ssl; +} + +const struct Curl_ssl Curl_ssl_mbedtls = { + { CURLSSLBACKEND_MBEDTLS, "mbedtls" }, /* info */ + + SSLSUPP_CA_PATH | + SSLSUPP_CAINFO_BLOB | + SSLSUPP_PINNEDPUBKEY | + SSLSUPP_SSL_CTX | + SSLSUPP_HTTPS_PROXY, + + sizeof(struct mbed_ssl_backend_data), + + mbedtls_init, /* init */ + mbedtls_cleanup, /* cleanup */ + mbedtls_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + Curl_none_shutdown, /* shutdown */ + mbedtls_data_pending, /* data_pending */ + mbedtls_random, /* random */ + Curl_none_cert_status_request, /* cert_status_request */ + mbedtls_connect, /* connect */ + mbedtls_connect_nonblocking, /* connect_nonblocking */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ + mbedtls_get_internals, /* get_internals */ + mbedtls_close, /* close_one */ + mbedtls_close_all, /* close_all */ + mbedtls_session_free, /* session_free */ + Curl_none_set_engine, /* set_engine */ + Curl_none_set_engine_default, /* set_engine_default */ + Curl_none_engines_list, /* engines_list */ + Curl_none_false_start, /* false_start */ + mbedtls_sha256sum, /* sha256sum */ + NULL, /* associate_connection */ + NULL, /* disassociate_connection */ + NULL, /* free_multi_ssl_backend_data */ + mbed_recv, /* recv decrypted data */ + mbed_send, /* send data to encrypt */ +}; + +#endif /* USE_MBEDTLS */ diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.h b/Utilities/cmcurl/lib/vtls/mbedtls.h new file mode 100644 index 0000000..d8a0a06 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/mbedtls.h @@ -0,0 +1,34 @@ +#ifndef HEADER_CURL_MBEDTLS_H +#define HEADER_CURL_MBEDTLS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com> + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_MBEDTLS + +extern const struct Curl_ssl Curl_ssl_mbedtls; + +#endif /* USE_MBEDTLS */ +#endif /* HEADER_CURL_MBEDTLS_H */ diff --git a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c new file mode 100644 index 0000000..22b1b22 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c @@ -0,0 +1,134 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com> + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if defined(USE_MBEDTLS) && \ + ((defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ + defined(USE_THREADS_WIN32)) + +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) +# include <pthread.h> +# define MBEDTLS_MUTEX_T pthread_mutex_t +#elif defined(USE_THREADS_WIN32) +# define MBEDTLS_MUTEX_T HANDLE +#endif + +#include "mbedtls_threadlock.h" +#include "curl_printf.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/* number of thread locks */ +#define NUMT 2 + +/* This array will store all of the mutexes available to Mbedtls. */ +static MBEDTLS_MUTEX_T *mutex_buf = NULL; + +int Curl_mbedtlsthreadlock_thread_setup(void) +{ + int i; + + mutex_buf = calloc(1, NUMT * sizeof(MBEDTLS_MUTEX_T)); + if(!mutex_buf) + return 0; /* error, no number of threads defined */ + + for(i = 0; i < NUMT; i++) { +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) + if(pthread_mutex_init(&mutex_buf[i], NULL)) + return 0; /* pthread_mutex_init failed */ +#elif defined(USE_THREADS_WIN32) + mutex_buf[i] = CreateMutex(0, FALSE, 0); + if(mutex_buf[i] == 0) + return 0; /* CreateMutex failed */ +#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */ + } + + return 1; /* OK */ +} + +int Curl_mbedtlsthreadlock_thread_cleanup(void) +{ + int i; + + if(!mutex_buf) + return 0; /* error, no threads locks defined */ + + for(i = 0; i < NUMT; i++) { +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) + if(pthread_mutex_destroy(&mutex_buf[i])) + return 0; /* pthread_mutex_destroy failed */ +#elif defined(USE_THREADS_WIN32) + if(!CloseHandle(mutex_buf[i])) + return 0; /* CloseHandle failed */ +#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */ + } + free(mutex_buf); + mutex_buf = NULL; + + return 1; /* OK */ +} + +int Curl_mbedtlsthreadlock_lock_function(int n) +{ + if(n < NUMT) { +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) + if(pthread_mutex_lock(&mutex_buf[n])) { + DEBUGF(fprintf(stderr, + "Error: mbedtlsthreadlock_lock_function failed\n")); + return 0; /* pthread_mutex_lock failed */ + } +#elif defined(USE_THREADS_WIN32) + if(WaitForSingleObject(mutex_buf[n], INFINITE) == WAIT_FAILED) { + DEBUGF(fprintf(stderr, + "Error: mbedtlsthreadlock_lock_function failed\n")); + return 0; /* pthread_mutex_lock failed */ + } +#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */ + } + return 1; /* OK */ +} + +int Curl_mbedtlsthreadlock_unlock_function(int n) +{ + if(n < NUMT) { +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) + if(pthread_mutex_unlock(&mutex_buf[n])) { + DEBUGF(fprintf(stderr, + "Error: mbedtlsthreadlock_unlock_function failed\n")); + return 0; /* pthread_mutex_unlock failed */ + } +#elif defined(USE_THREADS_WIN32) + if(!ReleaseMutex(mutex_buf[n])) { + DEBUGF(fprintf(stderr, + "Error: mbedtlsthreadlock_unlock_function failed\n")); + return 0; /* pthread_mutex_lock failed */ + } +#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */ + } + return 1; /* OK */ +} + +#endif /* USE_MBEDTLS */ diff --git a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h new file mode 100644 index 0000000..2b0bd41 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h @@ -0,0 +1,50 @@ +#ifndef HEADER_CURL_MBEDTLS_THREADLOCK_H +#define HEADER_CURL_MBEDTLS_THREADLOCK_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com> + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_MBEDTLS + +#if (defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ + defined(USE_THREADS_WIN32) + +int Curl_mbedtlsthreadlock_thread_setup(void); +int Curl_mbedtlsthreadlock_thread_cleanup(void); +int Curl_mbedtlsthreadlock_lock_function(int n); +int Curl_mbedtlsthreadlock_unlock_function(int n); + +#else + +#define Curl_mbedtlsthreadlock_thread_setup() 1 +#define Curl_mbedtlsthreadlock_thread_cleanup() 1 +#define Curl_mbedtlsthreadlock_lock_function(x) 1 +#define Curl_mbedtlsthreadlock_unlock_function(x) 1 + +#endif /* USE_THREADS_POSIX || USE_THREADS_WIN32 */ + +#endif /* USE_MBEDTLS */ + +#endif /* HEADER_CURL_MBEDTLS_THREADLOCK_H */ diff --git a/Utilities/cmcurl/lib/vtls/openssl.c b/Utilities/cmcurl/lib/vtls/openssl.c new file mode 100644 index 0000000..ca6d931 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/openssl.c @@ -0,0 +1,4952 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Source file for all OpenSSL-specific code for the TLS/SSL layer. No code + * but vtls.c should ever call or use these functions. + */ + +#include "curl_setup.h" + +#if defined(USE_QUICHE) || defined(USE_OPENSSL) + +#include <limits.h> + +/* Wincrypt must be included before anything that could include OpenSSL. */ +#if defined(USE_WIN32_CRYPTO) +#include <wincrypt.h> +/* Undefine wincrypt conflicting symbols for BoringSSL. */ +#undef X509_NAME +#undef X509_EXTENSIONS +#undef PKCS7_ISSUER_AND_SERIAL +#undef PKCS7_SIGNER_INFO +#undef OCSP_REQUEST +#undef OCSP_RESPONSE +#endif + +#include "urldata.h" +#include "sendf.h" +#include "formdata.h" /* for the boundary function */ +#include "url.h" /* for the ssl config check function */ +#include "inet_pton.h" +#include "openssl.h" +#include "connect.h" +#include "slist.h" +#include "select.h" +#include "vtls.h" +#include "vtls_int.h" +#include "vauth/vauth.h" +#include "keylog.h" +#include "strcase.h" +#include "hostcheck.h" +#include "multiif.h" +#include "strerror.h" +#include "curl_printf.h" + +#include <openssl/ssl.h> +#include <openssl/rand.h> +#include <openssl/x509v3.h> +#ifndef OPENSSL_NO_DSA +#include <openssl/dsa.h> +#endif +#include <openssl/dh.h> +#include <openssl/err.h> +#include <openssl/md5.h> +#include <openssl/conf.h> +#include <openssl/bn.h> +#include <openssl/rsa.h> +#include <openssl/bio.h> +#include <openssl/buffer.h> +#include <openssl/pkcs12.h> +#include <openssl/tls1.h> +#include <openssl/evp.h> + +#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_OCSP) +#include <openssl/ocsp.h> +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x0090700fL) && /* 0.9.7 or later */ \ + !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_UI_CONSOLE) +#define USE_OPENSSL_ENGINE +#include <openssl/engine.h> +#endif + +#include "warnless.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +/* Uncomment the ALLOW_RENEG line to a real #define if you want to allow TLS + renegotiations when built with BoringSSL. Renegotiating is non-compliant + with HTTP/2 and "an extremely dangerous protocol feature". Beware. + +#define ALLOW_RENEG 1 + */ + +#ifndef OPENSSL_VERSION_NUMBER +#error "OPENSSL_VERSION_NUMBER not defined" +#endif + +#ifdef USE_OPENSSL_ENGINE +#include <openssl/ui.h> +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x00909000L +#define SSL_METHOD_QUAL const +#else +#define SSL_METHOD_QUAL +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x10000000L) +#define HAVE_ERR_REMOVE_THREAD_STATE 1 +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && /* OpenSSL 1.1.0+ */ \ + !(defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER < 0x20700000L) +#define SSLEAY_VERSION_NUMBER OPENSSL_VERSION_NUMBER +#define HAVE_X509_GET0_EXTENSIONS 1 /* added in 1.1.0 -pre1 */ +#define HAVE_OPAQUE_EVP_PKEY 1 /* since 1.1.0 -pre3 */ +#define HAVE_OPAQUE_RSA_DSA_DH 1 /* since 1.1.0 -pre5 */ +#define CONST_EXTS const +#define HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED 1 + +/* funny typecast define due to difference in API */ +#ifdef LIBRESSL_VERSION_NUMBER +#define ARG2_X509_signature_print (X509_ALGOR *) +#else +#define ARG2_X509_signature_print +#endif + +#else +/* For OpenSSL before 1.1.0 */ +#define ASN1_STRING_get0_data(x) ASN1_STRING_data(x) +#define X509_get0_notBefore(x) X509_get_notBefore(x) +#define X509_get0_notAfter(x) X509_get_notAfter(x) +#define CONST_EXTS /* nope */ +#ifndef LIBRESSL_VERSION_NUMBER +#define OpenSSL_version_num() SSLeay() +#endif +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) && /* 1.0.2 or later */ \ + !(defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER < 0x20700000L) +#define HAVE_X509_GET0_SIGNATURE 1 +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) /* 1.0.2 or later */ +#define HAVE_SSL_GET_SHUTDOWN 1 +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x10002003L && \ + OPENSSL_VERSION_NUMBER <= 0x10002FFFL && \ + !defined(OPENSSL_NO_COMP) +#define HAVE_SSL_COMP_FREE_COMPRESSION_METHODS 1 +#endif + +#if (OPENSSL_VERSION_NUMBER < 0x0090808fL) +/* not present in older OpenSSL */ +#define OPENSSL_load_builtin_modules(x) +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) +#define HAVE_EVP_PKEY_GET_PARAMS 1 +#endif + +#ifdef HAVE_EVP_PKEY_GET_PARAMS +#include <openssl/core_names.h> +#define DECLARE_PKEY_PARAM_BIGNUM(name) BIGNUM *name = NULL +#define FREE_PKEY_PARAM_BIGNUM(name) BN_clear_free(name) +#else +#define DECLARE_PKEY_PARAM_BIGNUM(name) const BIGNUM *name +#define FREE_PKEY_PARAM_BIGNUM(name) +#endif + +/* + * Whether SSL_CTX_set_keylog_callback is available. + * OpenSSL: supported since 1.1.1 https://github.com/openssl/openssl/pull/2287 + * BoringSSL: supported since d28f59c27bac (committed 2015-11-19) + * LibreSSL: supported since 3.5.0 (released 2022-02-24) + */ +#if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) || \ + (defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER >= 0x3050000fL) || \ + defined(OPENSSL_IS_BORINGSSL) +#define HAVE_KEYLOG_CALLBACK +#endif + +/* Whether SSL_CTX_set_ciphersuites is available. + * OpenSSL: supported since 1.1.1 (commit a53b5be6a05) + * BoringSSL: no + * LibreSSL: supported since 3.4.1 (released 2021-10-14) + */ +#if ((OPENSSL_VERSION_NUMBER >= 0x10101000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) || \ + (defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER >= 0x3040100fL)) && \ + !defined(OPENSSL_IS_BORINGSSL) + #define HAVE_SSL_CTX_SET_CIPHERSUITES + #if !defined(OPENSSL_IS_AWSLC) + #define HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH + #endif +#endif + +/* + * Whether SSL_CTX_set1_curves_list is available. + * OpenSSL: supported since 1.0.2, see + * https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set1_groups.html + * BoringSSL: supported since 5fd1807d95f7 (committed 2016-09-30) + * LibreSSL: since 2.5.3 (April 12, 2017) + */ +#if ((OPENSSL_VERSION_NUMBER >= 0x10002000L) && \ + !(defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER < 0x20503000L)) || \ + defined(OPENSSL_IS_BORINGSSL) +#define HAVE_SSL_CTX_SET_EC_CURVES +#endif + +#if defined(LIBRESSL_VERSION_NUMBER) +#define OSSL_PACKAGE "LibreSSL" +#elif defined(OPENSSL_IS_BORINGSSL) +#define OSSL_PACKAGE "BoringSSL" +#elif defined(OPENSSL_IS_AWSLC) +#define OSSL_PACKAGE "AWS-LC" +#else +# if defined(USE_NGTCP2) && defined(USE_NGHTTP3) +# define OSSL_PACKAGE "quictls" +# else +# define OSSL_PACKAGE "OpenSSL" +#endif +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) +/* up2date versions of OpenSSL maintain reasonably secure defaults without + * breaking compatibility, so it is better not to override the defaults in curl + */ +#define DEFAULT_CIPHER_SELECTION NULL +#else +/* ... but it is not the case with old versions of OpenSSL */ +#define DEFAULT_CIPHER_SELECTION \ + "ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH" +#endif + +#ifdef HAVE_OPENSSL_SRP +/* the function exists */ +#ifdef USE_TLS_SRP +/* the functionality is not disabled */ +#define USE_OPENSSL_SRP +#endif +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) +#define HAVE_RANDOM_INIT_BY_DEFAULT 1 +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ + !(defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER < 0x2070100fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(OPENSSL_IS_AWSLC) +#define HAVE_OPENSSL_VERSION +#endif + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +typedef uint32_t sslerr_t; +#else +typedef unsigned long sslerr_t; +#endif + +/* + * Whether the OpenSSL version has the API needed to support sharing an + * X509_STORE between connections. The API is: + * * `X509_STORE_up_ref` -- Introduced: OpenSSL 1.1.0. + */ +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* OpenSSL >= 1.1.0 */ +#define HAVE_SSL_X509_STORE_SHARE +#endif + +/* FIXME: On a specific machine using LCC 1.23, OpenSSL 2.0.0 + * is found but does not seem to have X509_STORE_up_ref. */ +#if defined(__LCC__) && defined(__EDG__) && (__LCC__ == 123) +#undef HAVE_SSL_X509_STORE_SHARE +#endif + +/* What API version do we use? */ +#if defined(LIBRESSL_VERSION_NUMBER) +#define USE_PRE_1_1_API (LIBRESSL_VERSION_NUMBER < 0x2070000f) +#else /* !LIBRESSL_VERSION_NUMBER */ +#define USE_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L) +#endif /* !LIBRESSL_VERSION_NUMBER */ + +struct ossl_ssl_backend_data { + /* these ones requires specific SSL-types */ + SSL_CTX* ctx; + SSL* handle; + X509* server_cert; + BIO_METHOD *bio_method; + CURLcode io_result; /* result of last BIO cfilter operation */ +#ifndef HAVE_KEYLOG_CALLBACK + /* Set to true once a valid keylog entry has been created to avoid dupes. */ + bool keylog_done; +#endif + bool x509_store_setup; /* x509 store has been set up */ +}; + +#if defined(HAVE_SSL_X509_STORE_SHARE) +struct multi_ssl_backend_data { + char *CAfile; /* CAfile path used to generate X509 store */ + X509_STORE *store; /* cached X509 store or NULL if none */ + struct curltime time; /* when the cached store was created */ +}; +#endif /* HAVE_SSL_X509_STORE_SHARE */ + +#define push_certinfo(_label, _num) \ +do { \ + long info_len = BIO_get_mem_data(mem, &ptr); \ + Curl_ssl_push_certinfo_len(data, _num, _label, ptr, info_len); \ + if(1 != BIO_reset(mem)) \ + break; \ +} while(0) + +static void pubkey_show(struct Curl_easy *data, + BIO *mem, + int num, + const char *type, + const char *name, + const BIGNUM *bn) +{ + char *ptr; + char namebuf[32]; + + msnprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name); + + if(bn) + BN_print(mem, bn); + push_certinfo(namebuf, num); +} + +#ifdef HAVE_OPAQUE_RSA_DSA_DH +#define print_pubkey_BN(_type, _name, _num) \ + pubkey_show(data, mem, _num, #_type, #_name, _name) + +#else +#define print_pubkey_BN(_type, _name, _num) \ +do { \ + if(_type->_name) { \ + pubkey_show(data, mem, _num, #_type, #_name, _type->_name); \ + } \ +} while(0) +#endif + +static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len) +{ + int i, ilen; + + ilen = (int)len; + if(ilen < 0) + return 1; /* buffer too big */ + + i = i2t_ASN1_OBJECT(buf, ilen, a); + + if(i >= ilen) + return 1; /* buffer too small */ + + return 0; +} + +static void X509V3_ext(struct Curl_easy *data, + int certnum, + CONST_EXTS STACK_OF(X509_EXTENSION) *exts) +{ + int i; + + if((int)sk_X509_EXTENSION_num(exts) <= 0) + /* no extensions, bail out */ + return; + + for(i = 0; i < (int)sk_X509_EXTENSION_num(exts); i++) { + ASN1_OBJECT *obj; + X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i); + BUF_MEM *biomem; + char namebuf[128]; + BIO *bio_out = BIO_new(BIO_s_mem()); + + if(!bio_out) + return; + + obj = X509_EXTENSION_get_object(ext); + + asn1_object_dump(obj, namebuf, sizeof(namebuf)); + + if(!X509V3_EXT_print(bio_out, ext, 0, 0)) + ASN1_STRING_print(bio_out, (ASN1_STRING *)X509_EXTENSION_get_data(ext)); + + BIO_get_mem_ptr(bio_out, &biomem); + Curl_ssl_push_certinfo_len(data, certnum, namebuf, biomem->data, + biomem->length); + BIO_free(bio_out); + } +} + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +typedef size_t numcert_t; +#else +typedef int numcert_t; +#endif + +CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) +{ + CURLcode result; + STACK_OF(X509) *sk; + int i; + numcert_t numcerts; + BIO *mem; + + DEBUGASSERT(ssl); + + sk = SSL_get_peer_cert_chain(ssl); + if(!sk) { + return CURLE_OUT_OF_MEMORY; + } + + numcerts = sk_X509_num(sk); + + result = Curl_ssl_init_certinfo(data, (int)numcerts); + if(result) { + return result; + } + + mem = BIO_new(BIO_s_mem()); + if(!mem) { + return CURLE_OUT_OF_MEMORY; + } + + for(i = 0; i < (int)numcerts; i++) { + ASN1_INTEGER *num; + X509 *x = sk_X509_value(sk, i); + EVP_PKEY *pubkey = NULL; + int j; + char *ptr; + const ASN1_BIT_STRING *psig = NULL; + + X509_NAME_print_ex(mem, X509_get_subject_name(x), 0, XN_FLAG_ONELINE); + push_certinfo("Subject", i); + + X509_NAME_print_ex(mem, X509_get_issuer_name(x), 0, XN_FLAG_ONELINE); + push_certinfo("Issuer", i); + + BIO_printf(mem, "%lx", X509_get_version(x)); + push_certinfo("Version", i); + + num = X509_get_serialNumber(x); + if(num->type == V_ASN1_NEG_INTEGER) + BIO_puts(mem, "-"); + for(j = 0; j < num->length; j++) + BIO_printf(mem, "%02x", num->data[j]); + push_certinfo("Serial Number", i); + +#if defined(HAVE_X509_GET0_SIGNATURE) && defined(HAVE_X509_GET0_EXTENSIONS) + { + const X509_ALGOR *sigalg = NULL; + X509_PUBKEY *xpubkey = NULL; + ASN1_OBJECT *pubkeyoid = NULL; + + X509_get0_signature(&psig, &sigalg, x); + if(sigalg) { + const ASN1_OBJECT *sigalgoid = NULL; + X509_ALGOR_get0(&sigalgoid, NULL, NULL, sigalg); + i2a_ASN1_OBJECT(mem, sigalgoid); + push_certinfo("Signature Algorithm", i); + } + + xpubkey = X509_get_X509_PUBKEY(x); + if(xpubkey) { + X509_PUBKEY_get0_param(&pubkeyoid, NULL, NULL, NULL, xpubkey); + if(pubkeyoid) { + i2a_ASN1_OBJECT(mem, pubkeyoid); + push_certinfo("Public Key Algorithm", i); + } + } + + X509V3_ext(data, i, X509_get0_extensions(x)); + } +#else + { + /* before OpenSSL 1.0.2 */ + X509_CINF *cinf = x->cert_info; + + i2a_ASN1_OBJECT(mem, cinf->signature->algorithm); + push_certinfo("Signature Algorithm", i); + + i2a_ASN1_OBJECT(mem, cinf->key->algor->algorithm); + push_certinfo("Public Key Algorithm", i); + + X509V3_ext(data, i, cinf->extensions); + + psig = x->signature; + } +#endif + + ASN1_TIME_print(mem, X509_get0_notBefore(x)); + push_certinfo("Start date", i); + + ASN1_TIME_print(mem, X509_get0_notAfter(x)); + push_certinfo("Expire date", i); + + pubkey = X509_get_pubkey(x); + if(!pubkey) + infof(data, " Unable to load public key"); + else { + int pktype; +#ifdef HAVE_OPAQUE_EVP_PKEY + pktype = EVP_PKEY_id(pubkey); +#else + pktype = pubkey->type; +#endif + switch(pktype) { + case EVP_PKEY_RSA: + { +#ifndef HAVE_EVP_PKEY_GET_PARAMS + RSA *rsa; +#ifdef HAVE_OPAQUE_EVP_PKEY + rsa = EVP_PKEY_get0_RSA(pubkey); +#else + rsa = pubkey->pkey.rsa; +#endif /* HAVE_OPAQUE_EVP_PKEY */ +#endif /* !HAVE_EVP_PKEY_GET_PARAMS */ + + { +#ifdef HAVE_OPAQUE_RSA_DSA_DH + DECLARE_PKEY_PARAM_BIGNUM(n); + DECLARE_PKEY_PARAM_BIGNUM(e); +#ifdef HAVE_EVP_PKEY_GET_PARAMS + EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_N, &n); + EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_E, &e); +#else + RSA_get0_key(rsa, &n, &e, NULL); +#endif /* HAVE_EVP_PKEY_GET_PARAMS */ + BIO_printf(mem, "%d", n ? BN_num_bits(n) : 0); +#else + BIO_printf(mem, "%d", rsa->n ? BN_num_bits(rsa->n) : 0); +#endif /* HAVE_OPAQUE_RSA_DSA_DH */ + push_certinfo("RSA Public Key", i); + print_pubkey_BN(rsa, n, i); + print_pubkey_BN(rsa, e, i); + FREE_PKEY_PARAM_BIGNUM(n); + FREE_PKEY_PARAM_BIGNUM(e); + } + + break; + } + case EVP_PKEY_DSA: + { +#ifndef OPENSSL_NO_DSA +#ifndef HAVE_EVP_PKEY_GET_PARAMS + DSA *dsa; +#ifdef HAVE_OPAQUE_EVP_PKEY + dsa = EVP_PKEY_get0_DSA(pubkey); +#else + dsa = pubkey->pkey.dsa; +#endif /* HAVE_OPAQUE_EVP_PKEY */ +#endif /* !HAVE_EVP_PKEY_GET_PARAMS */ + { +#ifdef HAVE_OPAQUE_RSA_DSA_DH + DECLARE_PKEY_PARAM_BIGNUM(p); + DECLARE_PKEY_PARAM_BIGNUM(q); + DECLARE_PKEY_PARAM_BIGNUM(g); + DECLARE_PKEY_PARAM_BIGNUM(pub_key); +#ifdef HAVE_EVP_PKEY_GET_PARAMS + EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_P, &p); + EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_Q, &q); + EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_G, &g); + EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key); +#else + DSA_get0_pqg(dsa, &p, &q, &g); + DSA_get0_key(dsa, &pub_key, NULL); +#endif /* HAVE_EVP_PKEY_GET_PARAMS */ +#endif /* HAVE_OPAQUE_RSA_DSA_DH */ + print_pubkey_BN(dsa, p, i); + print_pubkey_BN(dsa, q, i); + print_pubkey_BN(dsa, g, i); + print_pubkey_BN(dsa, pub_key, i); + FREE_PKEY_PARAM_BIGNUM(p); + FREE_PKEY_PARAM_BIGNUM(q); + FREE_PKEY_PARAM_BIGNUM(g); + FREE_PKEY_PARAM_BIGNUM(pub_key); + } +#endif /* !OPENSSL_NO_DSA */ + break; + } + case EVP_PKEY_DH: + { +#ifndef HAVE_EVP_PKEY_GET_PARAMS + DH *dh; +#ifdef HAVE_OPAQUE_EVP_PKEY + dh = EVP_PKEY_get0_DH(pubkey); +#else + dh = pubkey->pkey.dh; +#endif /* HAVE_OPAQUE_EVP_PKEY */ +#endif /* !HAVE_EVP_PKEY_GET_PARAMS */ + { +#ifdef HAVE_OPAQUE_RSA_DSA_DH + DECLARE_PKEY_PARAM_BIGNUM(p); + DECLARE_PKEY_PARAM_BIGNUM(q); + DECLARE_PKEY_PARAM_BIGNUM(g); + DECLARE_PKEY_PARAM_BIGNUM(pub_key); +#ifdef HAVE_EVP_PKEY_GET_PARAMS + EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_P, &p); + EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_Q, &q); + EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_G, &g); + EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key); +#else + DH_get0_pqg(dh, &p, &q, &g); + DH_get0_key(dh, &pub_key, NULL); +#endif /* HAVE_EVP_PKEY_GET_PARAMS */ + print_pubkey_BN(dh, p, i); + print_pubkey_BN(dh, q, i); + print_pubkey_BN(dh, g, i); +#else + print_pubkey_BN(dh, p, i); + print_pubkey_BN(dh, g, i); +#endif /* HAVE_OPAQUE_RSA_DSA_DH */ + print_pubkey_BN(dh, pub_key, i); + FREE_PKEY_PARAM_BIGNUM(p); + FREE_PKEY_PARAM_BIGNUM(q); + FREE_PKEY_PARAM_BIGNUM(g); + FREE_PKEY_PARAM_BIGNUM(pub_key); + } + break; + } + } + EVP_PKEY_free(pubkey); + } + + if(psig) { + for(j = 0; j < psig->length; j++) + BIO_printf(mem, "%02x:", psig->data[j]); + push_certinfo("Signature", i); + } + + PEM_write_bio_X509(mem, x); + push_certinfo("Cert", i); + } + + BIO_free(mem); + + return CURLE_OK; +} + +#endif /* quiche or OpenSSL */ + +#ifdef USE_OPENSSL + +#if USE_PRE_1_1_API +#if !defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER < 0x2070000fL +#define BIO_set_init(x,v) ((x)->init=(v)) +#define BIO_get_data(x) ((x)->ptr) +#define BIO_set_data(x,v) ((x)->ptr=(v)) +#endif +#define BIO_get_shutdown(x) ((x)->shutdown) +#define BIO_set_shutdown(x,v) ((x)->shutdown=(v)) +#endif /* USE_PRE_1_1_API */ + +static int ossl_bio_cf_create(BIO *bio) +{ + BIO_set_shutdown(bio, 1); + BIO_set_init(bio, 1); +#if USE_PRE_1_1_API + bio->num = -1; +#endif + BIO_set_data(bio, NULL); + return 1; +} + +static int ossl_bio_cf_destroy(BIO *bio) +{ + if(!bio) + return 0; + return 1; +} + +static long ossl_bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr) +{ + struct Curl_cfilter *cf = BIO_get_data(bio); + long ret = 1; + + (void)cf; + (void)ptr; + switch(cmd) { + case BIO_CTRL_GET_CLOSE: + ret = (long)BIO_get_shutdown(bio); + break; + case BIO_CTRL_SET_CLOSE: + BIO_set_shutdown(bio, (int)num); + break; + case BIO_CTRL_FLUSH: + /* we do no delayed writes, but if we ever would, this + * needs to trigger it. */ + ret = 1; + break; + case BIO_CTRL_DUP: + ret = 1; + break; +#ifdef BIO_CTRL_EOF + case BIO_CTRL_EOF: + /* EOF has been reached on input? */ + return (!cf->next || !cf->next->connected); +#endif + default: + ret = 0; + break; + } + return ret; +} + +static int ossl_bio_cf_out_write(BIO *bio, const char *buf, int blen) +{ + struct Curl_cfilter *cf = BIO_get_data(bio); + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nwritten; + CURLcode result = CURLE_SEND_ERROR; + + DEBUGASSERT(data); + nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); + CURL_TRC_CF(data, cf, "ossl_bio_cf_out_write(len=%d) -> %d, err=%d", + blen, (int)nwritten, result); + BIO_clear_retry_flags(bio); + backend->io_result = result; + if(nwritten < 0) { + if(CURLE_AGAIN == result) + BIO_set_retry_write(bio); + } + return (int)nwritten; +} + +static int ossl_bio_cf_in_read(BIO *bio, char *buf, int blen) +{ + struct Curl_cfilter *cf = BIO_get_data(bio); + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nread; + CURLcode result = CURLE_RECV_ERROR; + + DEBUGASSERT(data); + /* OpenSSL catches this case, so should we. */ + if(!buf) + return 0; + + nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); + CURL_TRC_CF(data, cf, "ossl_bio_cf_in_read(len=%d) -> %d, err=%d", + blen, (int)nread, result); + BIO_clear_retry_flags(bio); + backend->io_result = result; + if(nread < 0) { + if(CURLE_AGAIN == result) + BIO_set_retry_read(bio); + } + + /* Before returning server replies to the SSL instance, we need + * to have setup the x509 store or verification will fail. */ + if(!backend->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, backend->ctx); + if(result) { + backend->io_result = result; + return -1; + } + backend->x509_store_setup = TRUE; + } + + return (int)nread; +} + +#if USE_PRE_1_1_API + +static BIO_METHOD ossl_bio_cf_meth_1_0 = { + BIO_TYPE_MEM, + "OpenSSL CF BIO", + ossl_bio_cf_out_write, + ossl_bio_cf_in_read, + NULL, /* puts is never called */ + NULL, /* gets is never called */ + ossl_bio_cf_ctrl, + ossl_bio_cf_create, + ossl_bio_cf_destroy, + NULL +}; + +static BIO_METHOD *ossl_bio_cf_method_create(void) +{ + return &ossl_bio_cf_meth_1_0; +} + +#define ossl_bio_cf_method_free(m) Curl_nop_stmt + +#else + +static BIO_METHOD *ossl_bio_cf_method_create(void) +{ + BIO_METHOD *m = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL CF BIO"); + if(m) { + BIO_meth_set_write(m, &ossl_bio_cf_out_write); + BIO_meth_set_read(m, &ossl_bio_cf_in_read); + BIO_meth_set_ctrl(m, &ossl_bio_cf_ctrl); + BIO_meth_set_create(m, &ossl_bio_cf_create); + BIO_meth_set_destroy(m, &ossl_bio_cf_destroy); + } + return m; +} + +static void ossl_bio_cf_method_free(BIO_METHOD *m) +{ + if(m) + BIO_meth_free(m); +} + +#endif + + +/* + * Number of bytes to read from the random number seed file. This must be + * a finite value (because some entropy "files" like /dev/urandom have + * an infinite length), but must be large enough to provide enough + * entropy to properly seed OpenSSL's PRNG. + */ +#define RAND_LOAD_LENGTH 1024 + +#ifdef HAVE_KEYLOG_CALLBACK +static void ossl_keylog_callback(const SSL *ssl, const char *line) +{ + (void)ssl; + + Curl_tls_keylog_write_line(line); +} +#else +/* + * ossl_log_tls12_secret is called by libcurl to make the CLIENT_RANDOMs if the + * OpenSSL being used doesn't have native support for doing that. + */ +static void +ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done) +{ + const SSL_SESSION *session = SSL_get_session(ssl); + unsigned char client_random[SSL3_RANDOM_SIZE]; + unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH]; + int master_key_length = 0; + + if(!session || *keylog_done) + return; + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ + !(defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER < 0x20700000L) + /* ssl->s3 is not checked in openssl 1.1.0-pre6, but let's assume that + * we have a valid SSL context if we have a non-NULL session. */ + SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE); + master_key_length = (int) + SSL_SESSION_get_master_key(session, master_key, SSL_MAX_MASTER_KEY_LENGTH); +#else + if(ssl->s3 && session->master_key_length > 0) { + master_key_length = session->master_key_length; + memcpy(master_key, session->master_key, session->master_key_length); + memcpy(client_random, ssl->s3->client_random, SSL3_RANDOM_SIZE); + } +#endif + + /* The handshake has not progressed sufficiently yet, or this is a TLS 1.3 + * session (when curl was built with older OpenSSL headers and running with + * newer OpenSSL runtime libraries). */ + if(master_key_length <= 0) + return; + + *keylog_done = true; + Curl_tls_keylog_write("CLIENT_RANDOM", client_random, + master_key, master_key_length); +} +#endif /* !HAVE_KEYLOG_CALLBACK */ + +static const char *SSL_ERROR_to_str(int err) +{ + switch(err) { + case SSL_ERROR_NONE: + return "SSL_ERROR_NONE"; + case SSL_ERROR_SSL: + return "SSL_ERROR_SSL"; + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "SSL_ERROR_WANT_X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + case SSL_ERROR_ZERO_RETURN: + return "SSL_ERROR_ZERO_RETURN"; + case SSL_ERROR_WANT_CONNECT: + return "SSL_ERROR_WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "SSL_ERROR_WANT_ACCEPT"; +#if defined(SSL_ERROR_WANT_ASYNC) + case SSL_ERROR_WANT_ASYNC: + return "SSL_ERROR_WANT_ASYNC"; +#endif +#if defined(SSL_ERROR_WANT_ASYNC_JOB) + case SSL_ERROR_WANT_ASYNC_JOB: + return "SSL_ERROR_WANT_ASYNC_JOB"; +#endif +#if defined(SSL_ERROR_WANT_EARLY) + case SSL_ERROR_WANT_EARLY: + return "SSL_ERROR_WANT_EARLY"; +#endif + default: + return "SSL_ERROR unknown"; + } +} + +static size_t ossl_version(char *buffer, size_t size); + +/* Return error string for last OpenSSL error + */ +static char *ossl_strerror(unsigned long error, char *buf, size_t size) +{ + size_t len; + DEBUGASSERT(size); + *buf = '\0'; + + len = ossl_version(buf, size); + DEBUGASSERT(len < (size - 2)); + if(len < (size - 2)) { + buf += len; + size -= (len + 2); + *buf++ = ':'; + *buf++ = ' '; + *buf = '\0'; + } + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + ERR_error_string_n((uint32_t)error, buf, size); +#else + ERR_error_string_n(error, buf, size); +#endif + + if(!*buf) { + strncpy(buf, (error ? "Unknown error" : "No error"), size); + buf[size - 1] = '\0'; + } + + return buf; +} + +static int passwd_callback(char *buf, int num, int encrypting, + void *global_passwd) +{ + DEBUGASSERT(0 == encrypting); + + if(!encrypting) { + int klen = curlx_uztosi(strlen((char *)global_passwd)); + if(num > klen) { + memcpy(buf, global_passwd, klen + 1); + return klen; + } + } + return 0; +} + +/* + * rand_enough() returns TRUE if we have seeded the random engine properly. + */ +static bool rand_enough(void) +{ + return (0 != RAND_status()) ? TRUE : FALSE; +} + +static CURLcode ossl_seed(struct Curl_easy *data) +{ + /* This might get called before it has been added to a multi handle */ + if(data->multi && data->multi->ssl_seeded) + return CURLE_OK; + + if(rand_enough()) { + /* OpenSSL 1.1.0+ should return here */ + if(data->multi) + data->multi->ssl_seeded = TRUE; + return CURLE_OK; + } +#ifdef HAVE_RANDOM_INIT_BY_DEFAULT + /* with OpenSSL 1.1.0+, a failed RAND_status is a showstopper */ + failf(data, "Insufficient randomness"); + return CURLE_SSL_CONNECT_ERROR; +#else + +#ifdef RANDOM_FILE + RAND_load_file(RANDOM_FILE, RAND_LOAD_LENGTH); + if(rand_enough()) + return CURLE_OK; +#endif + + /* fallback to a custom seeding of the PRNG using a hash based on a current + time */ + do { + unsigned char randb[64]; + size_t len = sizeof(randb); + size_t i, i_max; + for(i = 0, i_max = len / sizeof(struct curltime); i < i_max; ++i) { + struct curltime tv = Curl_now(); + Curl_wait_ms(1); + tv.tv_sec *= i + 1; + tv.tv_usec *= (unsigned int)i + 2; + tv.tv_sec ^= ((Curl_now().tv_sec + Curl_now().tv_usec) * + (i + 3)) << 8; + tv.tv_usec ^= (unsigned int) ((Curl_now().tv_sec + + Curl_now().tv_usec) * + (i + 4)) << 16; + memcpy(&randb[i * sizeof(struct curltime)], &tv, + sizeof(struct curltime)); + } + RAND_add(randb, (int)len, (double)len/2); + } while(!rand_enough()); + + { + /* generates a default path for the random seed file */ + char fname[256]; + fname[0] = 0; /* blank it first */ + RAND_file_name(fname, sizeof(fname)); + if(fname[0]) { + /* we got a file name to try */ + RAND_load_file(fname, RAND_LOAD_LENGTH); + if(rand_enough()) + return CURLE_OK; + } + } + + infof(data, "libcurl is now using a weak random seed"); + return (rand_enough() ? CURLE_OK : + CURLE_SSL_CONNECT_ERROR /* confusing error code */); +#endif +} + +#ifndef SSL_FILETYPE_ENGINE +#define SSL_FILETYPE_ENGINE 42 +#endif +#ifndef SSL_FILETYPE_PKCS12 +#define SSL_FILETYPE_PKCS12 43 +#endif +static int do_file_type(const char *type) +{ + if(!type || !type[0]) + return SSL_FILETYPE_PEM; + if(strcasecompare(type, "PEM")) + return SSL_FILETYPE_PEM; + if(strcasecompare(type, "DER")) + return SSL_FILETYPE_ASN1; + if(strcasecompare(type, "ENG")) + return SSL_FILETYPE_ENGINE; + if(strcasecompare(type, "P12")) + return SSL_FILETYPE_PKCS12; + return -1; +} + +#ifdef USE_OPENSSL_ENGINE +/* + * Supply default password to the engine user interface conversation. + * The password is passed by OpenSSL engine from ENGINE_load_private_key() + * last argument to the ui and can be obtained by UI_get0_user_data(ui) here. + */ +static int ssl_ui_reader(UI *ui, UI_STRING *uis) +{ + const char *password; + switch(UI_get_string_type(uis)) { + case UIT_PROMPT: + case UIT_VERIFY: + password = (const char *)UI_get0_user_data(ui); + if(password && (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD)) { + UI_set_result(ui, uis, password); + return 1; + } + default: + break; + } + return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); +} + +/* + * Suppress interactive request for a default password if available. + */ +static int ssl_ui_writer(UI *ui, UI_STRING *uis) +{ + switch(UI_get_string_type(uis)) { + case UIT_PROMPT: + case UIT_VERIFY: + if(UI_get0_user_data(ui) && + (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD)) { + return 1; + } + default: + break; + } + return (UI_method_get_writer(UI_OpenSSL()))(ui, uis); +} + +/* + * Check if a given string is a PKCS#11 URI + */ +static bool is_pkcs11_uri(const char *string) +{ + return (string && strncasecompare(string, "pkcs11:", 7)); +} + +#endif + +static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine); + +static int +SSL_CTX_use_certificate_blob(SSL_CTX *ctx, const struct curl_blob *blob, + int type, const char *key_passwd) +{ + int ret = 0; + 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)); + if(!in) + return CURLE_OUT_OF_MEMORY; + + if(type == SSL_FILETYPE_ASN1) { + /* j = ERR_R_ASN1_LIB; */ + x = d2i_X509_bio(in, NULL); + } + else if(type == SSL_FILETYPE_PEM) { + /* ERR_R_PEM_LIB; */ + x = PEM_read_bio_X509(in, NULL, + passwd_callback, (void *)key_passwd); + } + else { + ret = 0; + goto end; + } + + if(!x) { + ret = 0; + goto end; + } + + ret = SSL_CTX_use_certificate(ctx, x); +end: + X509_free(x); + BIO_free(in); + return ret; +} + +static int +SSL_CTX_use_PrivateKey_blob(SSL_CTX *ctx, const struct curl_blob *blob, + int type, const char *key_passwd) +{ + int ret = 0; + EVP_PKEY *pkey = NULL; + BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); + if(!in) + return CURLE_OUT_OF_MEMORY; + + if(type == SSL_FILETYPE_PEM) + pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback, + (void *)key_passwd); + else if(type == SSL_FILETYPE_ASN1) + pkey = d2i_PrivateKey_bio(in, NULL); + else { + ret = 0; + goto end; + } + if(!pkey) { + ret = 0; + goto end; + } + ret = SSL_CTX_use_PrivateKey(ctx, pkey); + EVP_PKEY_free(pkey); +end: + BIO_free(in); + return ret; +} + +static int +SSL_CTX_use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob, + const char *key_passwd) +{ +/* SSL_CTX_add1_chain_cert introduced in OpenSSL 1.0.2 */ +#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) && /* OpenSSL 1.0.2 or later */ \ + !(defined(LIBRESSL_VERSION_NUMBER) && \ + (LIBRESSL_VERSION_NUMBER < 0x2090100fL)) /* LibreSSL 2.9.1 or later */ + int ret = 0; + X509 *x = NULL; + void *passwd_callback_userdata = (void *)key_passwd; + BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); + if(!in) + return CURLE_OUT_OF_MEMORY; + + ERR_clear_error(); + + x = PEM_read_bio_X509_AUX(in, NULL, + passwd_callback, (void *)key_passwd); + + if(!x) { + ret = 0; + goto end; + } + + ret = SSL_CTX_use_certificate(ctx, x); + + if(ERR_peek_error() != 0) + ret = 0; + + if(ret) { + X509 *ca; + sslerr_t err; + + if(!SSL_CTX_clear_chain_certs(ctx)) { + ret = 0; + goto end; + } + + while((ca = PEM_read_bio_X509(in, NULL, passwd_callback, + passwd_callback_userdata)) + != NULL) { + + if(!SSL_CTX_add0_chain_cert(ctx, ca)) { + X509_free(ca); + ret = 0; + goto end; + } + } + + err = ERR_peek_last_error(); + if((ERR_GET_LIB(err) == ERR_LIB_PEM) && + (ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) + ERR_clear_error(); + else + ret = 0; + } + +end: + X509_free(x); + BIO_free(in); + return ret; +#else + (void)ctx; /* unused */ + (void)blob; /* unused */ + (void)key_passwd; /* unused */ + return 0; +#endif +} + +static +int cert_stuff(struct Curl_easy *data, + SSL_CTX* ctx, + char *cert_file, + const struct curl_blob *cert_blob, + const char *cert_type, + char *key_file, + const struct curl_blob *key_blob, + const char *key_type, + char *key_passwd) +{ + char error_buffer[256]; + bool check_privkey = TRUE; + + int file_type = do_file_type(cert_type); + + if(cert_file || cert_blob || (file_type == SSL_FILETYPE_ENGINE)) { + SSL *ssl; + X509 *x509; + int cert_done = 0; + int cert_use_result; + + if(key_passwd) { + /* set the password in the callback userdata */ + SSL_CTX_set_default_passwd_cb_userdata(ctx, key_passwd); + /* Set passwd callback: */ + SSL_CTX_set_default_passwd_cb(ctx, passwd_callback); + } + + + switch(file_type) { + case SSL_FILETYPE_PEM: + /* SSL_CTX_use_certificate_chain_file() only works on PEM files */ + cert_use_result = cert_blob ? + SSL_CTX_use_certificate_chain_blob(ctx, cert_blob, key_passwd) : + SSL_CTX_use_certificate_chain_file(ctx, cert_file); + if(cert_use_result != 1) { + failf(data, + "could not load PEM client certificate from %s, " OSSL_PACKAGE + " error %s, " + "(no key found, wrong pass phrase, or wrong file format?)", + (cert_blob ? "CURLOPT_SSLCERT_BLOB" : cert_file), + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer)) ); + return 0; + } + break; + + case SSL_FILETYPE_ASN1: + /* SSL_CTX_use_certificate_file() works with either PEM or ASN1, but + we use the case above for PEM so this can only be performed with + ASN1 files. */ + + cert_use_result = cert_blob ? + SSL_CTX_use_certificate_blob(ctx, cert_blob, + file_type, key_passwd) : + SSL_CTX_use_certificate_file(ctx, cert_file, file_type); + if(cert_use_result != 1) { + failf(data, + "could not load ASN1 client certificate from %s, " OSSL_PACKAGE + " error %s, " + "(no key found, wrong pass phrase, or wrong file format?)", + (cert_blob ? "CURLOPT_SSLCERT_BLOB" : cert_file), + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer)) ); + return 0; + } + break; + case SSL_FILETYPE_ENGINE: +#if defined(USE_OPENSSL_ENGINE) && defined(ENGINE_CTRL_GET_CMD_FROM_NAME) + { + /* Implicitly use pkcs11 engine if none was provided and the + * cert_file is a PKCS#11 URI */ + if(!data->state.engine) { + if(is_pkcs11_uri(cert_file)) { + if(ossl_set_engine(data, "pkcs11") != CURLE_OK) { + return 0; + } + } + } + + if(data->state.engine) { + const char *cmd_name = "LOAD_CERT_CTRL"; + struct { + const char *cert_id; + X509 *cert; + } params; + + params.cert_id = cert_file; + params.cert = NULL; + + /* Does the engine supports LOAD_CERT_CTRL ? */ + if(!ENGINE_ctrl(data->state.engine, ENGINE_CTRL_GET_CMD_FROM_NAME, + 0, (void *)cmd_name, NULL)) { + failf(data, "ssl engine does not support loading certificates"); + return 0; + } + + /* Load the certificate from the engine */ + if(!ENGINE_ctrl_cmd(data->state.engine, cmd_name, + 0, ¶ms, NULL, 1)) { + failf(data, "ssl engine cannot load client cert with id" + " '%s' [%s]", cert_file, + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + return 0; + } + + if(!params.cert) { + failf(data, "ssl engine didn't initialized the certificate " + "properly."); + return 0; + } + + if(SSL_CTX_use_certificate(ctx, params.cert) != 1) { + failf(data, "unable to set client certificate [%s]", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + return 0; + } + X509_free(params.cert); /* we don't need the handle any more... */ + } + else { + failf(data, "crypto engine not set, can't load certificate"); + return 0; + } + } + break; +#else + failf(data, "file type ENG for certificate not implemented"); + return 0; +#endif + + case SSL_FILETYPE_PKCS12: + { + BIO *cert_bio = NULL; + PKCS12 *p12 = NULL; + EVP_PKEY *pri; + STACK_OF(X509) *ca = NULL; + if(cert_blob) { + 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, + sizeof(error_buffer)) ); + return 0; + } + } + else { + cert_bio = BIO_new(BIO_s_file()); + if(!cert_bio) { + failf(data, + "BIO_new return NULL, " OSSL_PACKAGE + " error %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer)) ); + return 0; + } + + if(BIO_read_filename(cert_bio, cert_file) <= 0) { + failf(data, "could not open PKCS12 file '%s'", cert_file); + BIO_free(cert_bio); + return 0; + } + } + + p12 = d2i_PKCS12_bio(cert_bio, NULL); + BIO_free(cert_bio); + + if(!p12) { + failf(data, "error reading PKCS12 file '%s'", + cert_blob ? "(memory blob)" : cert_file); + return 0; + } + + PKCS12_PBE_add(); + + if(!PKCS12_parse(p12, key_passwd, &pri, &x509, + &ca)) { + failf(data, + "could not parse PKCS12 file, check password, " OSSL_PACKAGE + " error %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer)) ); + PKCS12_free(p12); + return 0; + } + + PKCS12_free(p12); + + if(SSL_CTX_use_certificate(ctx, x509) != 1) { + failf(data, + "could not load PKCS12 client certificate, " OSSL_PACKAGE + " error %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer)) ); + goto fail; + } + + if(SSL_CTX_use_PrivateKey(ctx, pri) != 1) { + failf(data, "unable to use private key from PKCS12 file '%s'", + cert_file); + goto fail; + } + + if(!SSL_CTX_check_private_key (ctx)) { + failf(data, "private key from PKCS12 file '%s' " + "does not match certificate in same file", cert_file); + goto fail; + } + /* Set Certificate Verification chain */ + if(ca) { + while(sk_X509_num(ca)) { + /* + * Note that sk_X509_pop() is used below to make sure the cert is + * removed from the stack properly before getting passed to + * SSL_CTX_add_extra_chain_cert(), which takes ownership. Previously + * we used sk_X509_value() instead, but then we'd clean it in the + * subsequent sk_X509_pop_free() call. + */ + X509 *x = sk_X509_pop(ca); + if(!SSL_CTX_add_client_CA(ctx, x)) { + X509_free(x); + failf(data, "cannot add certificate to client CA list"); + goto fail; + } + if(!SSL_CTX_add_extra_chain_cert(ctx, x)) { + X509_free(x); + failf(data, "cannot add certificate to certificate chain"); + goto fail; + } + } + } + + cert_done = 1; +fail: + EVP_PKEY_free(pri); + X509_free(x509); + sk_X509_pop_free(ca, X509_free); + if(!cert_done) + return 0; /* failure! */ + break; + } + default: + failf(data, "not supported file type '%s' for certificate", cert_type); + return 0; + } + + if((!key_file) && (!key_blob)) { + key_file = cert_file; + key_blob = cert_blob; + } + else + file_type = do_file_type(key_type); + + switch(file_type) { + case SSL_FILETYPE_PEM: + if(cert_done) + break; + /* FALLTHROUGH */ + case SSL_FILETYPE_ASN1: + cert_use_result = key_blob ? + SSL_CTX_use_PrivateKey_blob(ctx, key_blob, file_type, key_passwd) : + SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type); + if(cert_use_result != 1) { + failf(data, "unable to set private key file: '%s' type %s", + key_file?key_file:"(memory blob)", key_type?key_type:"PEM"); + return 0; + } + break; + case SSL_FILETYPE_ENGINE: +#ifdef USE_OPENSSL_ENGINE + { + EVP_PKEY *priv_key = NULL; + + /* Implicitly use pkcs11 engine if none was provided and the + * key_file is a PKCS#11 URI */ + if(!data->state.engine) { + if(is_pkcs11_uri(key_file)) { + if(ossl_set_engine(data, "pkcs11") != CURLE_OK) { + return 0; + } + } + } + + if(data->state.engine) { + UI_METHOD *ui_method = + UI_create_method((char *)"curl user interface"); + if(!ui_method) { + failf(data, "unable do create " OSSL_PACKAGE + " user-interface method"); + return 0; + } + UI_method_set_opener(ui_method, UI_method_get_opener(UI_OpenSSL())); + UI_method_set_closer(ui_method, UI_method_get_closer(UI_OpenSSL())); + UI_method_set_reader(ui_method, ssl_ui_reader); + UI_method_set_writer(ui_method, ssl_ui_writer); + priv_key = ENGINE_load_private_key(data->state.engine, key_file, + ui_method, + key_passwd); + UI_destroy_method(ui_method); + if(!priv_key) { + failf(data, "failed to load private key from crypto engine"); + return 0; + } + if(SSL_CTX_use_PrivateKey(ctx, priv_key) != 1) { + failf(data, "unable to set private key"); + EVP_PKEY_free(priv_key); + return 0; + } + EVP_PKEY_free(priv_key); /* we don't need the handle any more... */ + } + else { + failf(data, "crypto engine not set, can't load private key"); + return 0; + } + } + break; +#else + failf(data, "file type ENG for private key not supported"); + return 0; +#endif + case SSL_FILETYPE_PKCS12: + if(!cert_done) { + failf(data, "file type P12 for private key not supported"); + return 0; + } + break; + default: + failf(data, "not supported file type for private key"); + return 0; + } + + ssl = SSL_new(ctx); + if(!ssl) { + failf(data, "unable to create an SSL structure"); + return 0; + } + + x509 = SSL_get_certificate(ssl); + + /* This version was provided by Evan Jordan and is supposed to not + leak memory as the previous version: */ + if(x509) { + EVP_PKEY *pktmp = X509_get_pubkey(x509); + EVP_PKEY_copy_parameters(pktmp, SSL_get_privatekey(ssl)); + EVP_PKEY_free(pktmp); + } + +#if !defined(OPENSSL_NO_RSA) && !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(OPENSSL_NO_DEPRECATED_3_0) + { + /* If RSA is used, don't check the private key if its flags indicate + * it doesn't support it. */ + EVP_PKEY *priv_key = SSL_get_privatekey(ssl); + int pktype; +#ifdef HAVE_OPAQUE_EVP_PKEY + pktype = EVP_PKEY_id(priv_key); +#else + pktype = priv_key->type; +#endif + if(pktype == EVP_PKEY_RSA) { + RSA *rsa = EVP_PKEY_get1_RSA(priv_key); + if(RSA_flags(rsa) & RSA_METHOD_FLAG_NO_CHECK) + check_privkey = FALSE; + RSA_free(rsa); /* Decrement reference count */ + } + } +#endif + + SSL_free(ssl); + + /* If we are using DSA, we can copy the parameters from + * the private key */ + + if(check_privkey == TRUE) { + /* Now we know that a key and cert have been set against + * the SSL context */ + if(!SSL_CTX_check_private_key(ctx)) { + failf(data, "Private key does not match the certificate public key"); + return 0; + } + } + } + return 1; +} + +CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data, SSL_CTX *ctx, + char *cert_file, + const struct curl_blob *cert_blob, + const char *cert_type, char *key_file, + const struct curl_blob *key_blob, + const char *key_type, char *key_passwd) +{ + int rv = cert_stuff(data, ctx, cert_file, cert_blob, cert_type, key_file, + key_blob, key_type, key_passwd); + if(rv != 1) { + return CURLE_SSL_CERTPROBLEM; + } + + return CURLE_OK; +} + +/* returns non-zero on failure */ +static int x509_name_oneline(X509_NAME *a, char *buf, size_t size) +{ + BIO *bio_out = BIO_new(BIO_s_mem()); + BUF_MEM *biomem; + int rc; + + if(!bio_out) + return 1; /* alloc failed! */ + + rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_SPLUS_SPC); + BIO_get_mem_ptr(bio_out, &biomem); + + if((size_t)biomem->length < size) + size = biomem->length; + else + size--; /* don't overwrite the buffer end */ + + memcpy(buf, biomem->data, size); + buf[size] = 0; + + BIO_free(bio_out); + + return !rc; +} + +/** + * Global SSL init + * + * @retval 0 error initializing SSL + * @retval 1 SSL initialized successfully + */ +static int ossl_init(void) +{ +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ + (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x2070000fL) + const uint64_t flags = +#ifdef OPENSSL_INIT_ENGINE_ALL_BUILTIN + /* not present in BoringSSL */ + OPENSSL_INIT_ENGINE_ALL_BUILTIN | +#endif +#ifdef CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG + OPENSSL_INIT_NO_LOAD_CONFIG | +#else + OPENSSL_INIT_LOAD_CONFIG | +#endif + 0; + OPENSSL_init_ssl(flags, NULL); +#else + OPENSSL_load_builtin_modules(); + +#ifdef USE_OPENSSL_ENGINE + ENGINE_load_builtin_engines(); +#endif + +/* CONF_MFLAGS_DEFAULT_SECTION was introduced some time between 0.9.8b and + 0.9.8e */ +#ifndef CONF_MFLAGS_DEFAULT_SECTION +#define CONF_MFLAGS_DEFAULT_SECTION 0x0 +#endif + +#ifndef CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG + CONF_modules_load_file(NULL, NULL, + CONF_MFLAGS_DEFAULT_SECTION| + CONF_MFLAGS_IGNORE_MISSING_FILE); +#endif + + /* Let's get nice error messages */ + SSL_load_error_strings(); + + /* Init the global ciphers and digests */ + if(!SSLeay_add_ssl_algorithms()) + return 0; + + OpenSSL_add_all_algorithms(); +#endif + + Curl_tls_keylog_open(); + + return 1; +} + +/* Global cleanup */ +static void ossl_cleanup(void) +{ +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ + !defined(LIBRESSL_VERSION_NUMBER) + /* OpenSSL 1.1 deprecates all these cleanup functions and + turns them into no-ops in OpenSSL 1.0 compatibility mode */ +#else + /* Free ciphers and digests lists */ + EVP_cleanup(); + +#ifdef USE_OPENSSL_ENGINE + /* Free engine list */ + ENGINE_cleanup(); +#endif + + /* Free OpenSSL error strings */ + ERR_free_strings(); + + /* Free thread local error state, destroying hash upon zero refcount */ +#ifdef HAVE_ERR_REMOVE_THREAD_STATE + ERR_remove_thread_state(NULL); +#else + ERR_remove_state(0); +#endif + + /* Free all memory allocated by all configuration modules */ + CONF_modules_free(); + +#ifdef HAVE_SSL_COMP_FREE_COMPRESSION_METHODS + SSL_COMP_free_compression_methods(); +#endif +#endif + + Curl_tls_keylog_close(); +} + +/* Selects an OpenSSL crypto engine + */ +static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine) +{ +#ifdef USE_OPENSSL_ENGINE + ENGINE *e; + +#if OPENSSL_VERSION_NUMBER >= 0x00909000L + e = ENGINE_by_id(engine); +#else + /* avoid memory leak */ + for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) { + const char *e_id = ENGINE_get_id(e); + if(!strcmp(engine, e_id)) + break; + } +#endif + + if(!e) { + failf(data, "SSL Engine '%s' not found", engine); + return CURLE_SSL_ENGINE_NOTFOUND; + } + + if(data->state.engine) { + ENGINE_finish(data->state.engine); + ENGINE_free(data->state.engine); + data->state.engine = NULL; + } + if(!ENGINE_init(e)) { + char buf[256]; + + ENGINE_free(e); + failf(data, "Failed to initialise SSL Engine '%s': %s", + engine, ossl_strerror(ERR_get_error(), buf, sizeof(buf))); + return CURLE_SSL_ENGINE_INITFAILED; + } + data->state.engine = e; + return CURLE_OK; +#else + (void)engine; + failf(data, "SSL Engine not supported"); + return CURLE_SSL_ENGINE_NOTFOUND; +#endif +} + +/* Sets engine as default for all SSL operations + */ +static CURLcode ossl_set_engine_default(struct Curl_easy *data) +{ +#ifdef USE_OPENSSL_ENGINE + if(data->state.engine) { + if(ENGINE_set_default(data->state.engine, ENGINE_METHOD_ALL) > 0) { + infof(data, "set default crypto engine '%s'", + ENGINE_get_id(data->state.engine)); + } + else { + failf(data, "set default crypto engine '%s' failed", + ENGINE_get_id(data->state.engine)); + return CURLE_SSL_ENGINE_SETFAILED; + } + } +#else + (void) data; +#endif + return CURLE_OK; +} + +/* Return list of OpenSSL crypto engine names. + */ +static struct curl_slist *ossl_engines_list(struct Curl_easy *data) +{ + struct curl_slist *list = NULL; +#ifdef USE_OPENSSL_ENGINE + struct curl_slist *beg; + ENGINE *e; + + for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) { + beg = curl_slist_append(list, ENGINE_get_id(e)); + if(!beg) { + curl_slist_free_all(list); + return NULL; + } + list = beg; + } +#endif + (void) data; + return list; +} + +static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + + (void)data; + DEBUGASSERT(backend); + + if(backend->handle) { + if(cf->next && cf->next->connected) { + char buf[1024]; + int nread, err; + long sslerr; + + /* Maybe the server has already sent a close notify alert. + Read it to avoid an RST on the TCP connection. */ + (void)SSL_read(backend->handle, buf, (int)sizeof(buf)); + ERR_clear_error(); + if(SSL_shutdown(backend->handle) == 1) { + CURL_TRC_CF(data, cf, "SSL shutdown finished"); + } + else { + nread = SSL_read(backend->handle, buf, (int)sizeof(buf)); + err = SSL_get_error(backend->handle, nread); + switch(err) { + case SSL_ERROR_NONE: /* this is not an error */ + case SSL_ERROR_ZERO_RETURN: /* no more data */ + CURL_TRC_CF(data, cf, "SSL shutdown, EOF from server"); + break; + case SSL_ERROR_WANT_READ: + /* SSL has send its notify and now wants to read the reply + * from the server. We are not really interested in that. */ + CURL_TRC_CF(data, cf, "SSL shutdown sent"); + break; + case SSL_ERROR_WANT_WRITE: + CURL_TRC_CF(data, cf, "SSL shutdown send blocked"); + break; + default: + sslerr = ERR_get_error(); + CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s', errno %d", + (sslerr ? + ossl_strerror(sslerr, buf, sizeof(buf)) : + SSL_ERROR_to_str(err)), + SOCKERRNO); + break; + } + } + + ERR_clear_error(); + SSL_set_connect_state(backend->handle); + } + + SSL_free(backend->handle); + backend->handle = NULL; + } + if(backend->ctx) { + SSL_CTX_free(backend->ctx); + backend->ctx = NULL; + backend->x509_store_setup = FALSE; + } + if(backend->bio_method) { + ossl_bio_cf_method_free(backend->bio_method); + backend->bio_method = NULL; + } +} + +/* + * This function is called to shut down the SSL layer but keep the + * socket open (CCC - Clear Command Channel) + */ +static int ossl_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + int retval = 0; + struct ssl_connect_data *connssl = cf->ctx; + char buf[256]; /* We will use this for the OpenSSL error buffer, so it has + to be at least 256 bytes long. */ + unsigned long sslerror; + int nread; + int buffsize; + int err; + bool done = FALSE; + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + int loop = 10; + + DEBUGASSERT(backend); + +#ifndef CURL_DISABLE_FTP + /* This has only been tested on the proftpd server, and the mod_tls code + sends a close notify alert without waiting for a close notify alert in + response. Thus we wait for a close notify alert from the server, but + we do not send one. Let's hope other servers do the same... */ + + if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) + (void)SSL_shutdown(backend->handle); +#endif + + if(backend->handle) { + buffsize = (int)sizeof(buf); + while(!done && loop--) { + int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), + SSL_SHUTDOWN_TIMEOUT); + if(what > 0) { + ERR_clear_error(); + + /* Something to read, let's do it and hope that it is the close + notify alert from the server */ + nread = SSL_read(backend->handle, buf, buffsize); + err = SSL_get_error(backend->handle, nread); + + switch(err) { + case SSL_ERROR_NONE: /* this is not an error */ + case SSL_ERROR_ZERO_RETURN: /* no more data */ + /* This is the expected response. There was no data but only + the close notify alert */ + done = TRUE; + break; + case SSL_ERROR_WANT_READ: + /* there's data pending, re-invoke SSL_read() */ + infof(data, "SSL_ERROR_WANT_READ"); + break; + case SSL_ERROR_WANT_WRITE: + /* SSL wants a write. Really odd. Let's bail out. */ + infof(data, "SSL_ERROR_WANT_WRITE"); + done = TRUE; + break; + default: + /* openssl/ssl.h says "look at error stack/return value/errno" */ + sslerror = ERR_get_error(); + failf(data, OSSL_PACKAGE " SSL_read on shutdown: %s, errno %d", + (sslerror ? + ossl_strerror(sslerror, buf, sizeof(buf)) : + SSL_ERROR_to_str(err)), + SOCKERRNO); + done = TRUE; + break; + } + } + else if(0 == what) { + /* timeout */ + failf(data, "SSL shutdown timeout"); + done = TRUE; + } + else { + /* anything that gets here is fatally bad */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + retval = -1; + done = TRUE; + } + } /* while()-loop for the select() */ + + if(data->set.verbose) { +#ifdef HAVE_SSL_GET_SHUTDOWN + switch(SSL_get_shutdown(backend->handle)) { + case SSL_SENT_SHUTDOWN: + infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN"); + break; + case SSL_RECEIVED_SHUTDOWN: + infof(data, "SSL_get_shutdown() returned SSL_RECEIVED_SHUTDOWN"); + break; + case SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN: + infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN|" + "SSL_RECEIVED__SHUTDOWN"); + break; + } +#endif + } + + SSL_free(backend->handle); + backend->handle = NULL; + } + return retval; +} + +static void ossl_session_free(void *ptr) +{ + /* free the ID */ + SSL_SESSION_free(ptr); +} + +/* + * This function is called when the 'data' struct is going away. Close + * down everything and free all resources! + */ +static void ossl_close_all(struct Curl_easy *data) +{ +#ifdef USE_OPENSSL_ENGINE + if(data->state.engine) { + ENGINE_finish(data->state.engine); + ENGINE_free(data->state.engine); + data->state.engine = NULL; + } +#else + (void)data; +#endif +#if !defined(HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED) && \ + defined(HAVE_ERR_REMOVE_THREAD_STATE) + /* OpenSSL 1.0.1 and 1.0.2 build an error queue that is stored per-thread + so we need to clean it here in case the thread will be killed. All OpenSSL + code should extract the error in association with the error so clearing + this queue here should be harmless at worst. */ + ERR_remove_thread_state(NULL); +#endif +} + +/* ====================================================== */ + +/* + * Match subjectAltName against the host name. + */ +static bool subj_alt_hostcheck(struct Curl_easy *data, + const char *match_pattern, + size_t matchlen, + const char *hostname, + size_t hostlen, + const char *dispname) +{ +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)dispname; + (void)data; +#endif + if(Curl_cert_hostcheck(match_pattern, matchlen, hostname, hostlen)) { + infof(data, " subjectAltName: host \"%s\" matched cert's \"%s\"", + dispname, match_pattern); + return TRUE; + } + return FALSE; +} + +/* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + This function is now used from ngtcp2 (QUIC) as well. +*/ +CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, + struct ssl_peer *peer, X509 *server_cert) +{ + bool matched = FALSE; + int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */ + size_t addrlen = 0; + STACK_OF(GENERAL_NAME) *altnames; +#ifdef ENABLE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + CURLcode result = CURLE_OK; + bool dNSName = FALSE; /* if a dNSName field exists in the cert */ + bool iPAddress = FALSE; /* if a iPAddress field exists in the cert */ + size_t hostlen; + + (void)conn; + hostlen = strlen(peer->hostname); + if(peer->is_ip_address) { +#ifdef ENABLE_IPV6 + if(conn->bits.ipv6_ip && + Curl_inet_pton(AF_INET6, peer->hostname, &addr)) { + target = GEN_IPADD; + addrlen = sizeof(struct in6_addr); + } + else +#endif + if(Curl_inet_pton(AF_INET, peer->hostname, &addr)) { + target = GEN_IPADD; + addrlen = sizeof(struct in_addr); + } + } + + /* get a "list" of alternative names */ + altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL); + + if(altnames) { +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + size_t numalts; + size_t i; +#else + int numalts; + int i; +#endif + bool dnsmatched = FALSE; + bool ipmatched = FALSE; + + /* get amount of alternatives, RFC2459 claims there MUST be at least + one, but we don't depend on it... */ + numalts = sk_GENERAL_NAME_num(altnames); + + /* loop through all alternatives - until a dnsmatch */ + for(i = 0; (i < numalts) && !dnsmatched; i++) { + /* get a handle to alternative name number i */ + const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i); + + if(check->type == GEN_DNS) + dNSName = TRUE; + else if(check->type == GEN_IPADD) + iPAddress = TRUE; + + /* only check alternatives of the same type the target is */ + if(check->type == target) { + /* get data and length */ + const char *altptr = (char *)ASN1_STRING_get0_data(check->d.ia5); + size_t altlen = (size_t) ASN1_STRING_length(check->d.ia5); + + 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 will depend on the actual string + type itself: for example for an IA5String the data will be 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 isn't true, there was an embedded zero in the name + string and we cannot match it. */ + subj_alt_hostcheck(data, altptr, altlen, + peer->hostname, hostlen, + peer->dispname)) { + dnsmatched = TRUE; + } + break; + + case GEN_IPADD: /* IP address comparison */ + /* compare alternative IP address if the data chunk is the same size + our server IP address is */ + if((altlen == addrlen) && !memcmp(altptr, &addr, altlen)) { + ipmatched = TRUE; + infof(data, + " subjectAltName: host \"%s\" matched cert's IP address!", + peer->dispname); + } + break; + } + } + } + GENERAL_NAMES_free(altnames); + + if(dnsmatched || ipmatched) + matched = TRUE; + } + + if(matched) + /* an alternative name matched */ + ; + else if(dNSName || iPAddress) { + infof(data, " subjectAltName does not match %s", peer->dispname); + failf(data, "SSL: no alternative certificate subject name matches " + "target host name '%s'", peer->dispname); + result = CURLE_PEER_FAILED_VERIFICATION; + } + else { + /* we have to look to the last occurrence of a commonName in the + distinguished one to get the most significant one. */ + int i = -1; + unsigned char *peer_CN = NULL; + int peerlen = 0; + + /* The following is done because of a bug in 0.9.6b */ + X509_NAME *name = X509_get_subject_name(server_cert); + if(name) { + int j; + while((j = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0) + i = j; + } + + /* we have the name entry and we will now convert this to a string + that we can use for comparison. Doing this we support BMPstring, + UTF8, etc. */ + + if(i >= 0) { + ASN1_STRING *tmp = + X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i)); + + /* In OpenSSL 0.9.7d and earlier, ASN1_STRING_to_UTF8 fails if the input + is already UTF-8 encoded. We check for this case and copy the raw + string manually to avoid the problem. This code can be made + conditional in the future when OpenSSL has been fixed. */ + if(tmp) { + if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { + peerlen = ASN1_STRING_length(tmp); + if(peerlen >= 0) { + peer_CN = OPENSSL_malloc(peerlen + 1); + if(peer_CN) { + memcpy(peer_CN, ASN1_STRING_get0_data(tmp), peerlen); + peer_CN[peerlen] = '\0'; + } + else + result = CURLE_OUT_OF_MEMORY; + } + } + else /* not a UTF8 name */ + peerlen = ASN1_STRING_to_UTF8(&peer_CN, tmp); + + if(peer_CN && (curlx_uztosi(strlen((char *)peer_CN)) != peerlen)) { + /* there was a terminating zero before the end of string, this + cannot match and we return failure! */ + failf(data, "SSL: illegal cert name field"); + result = CURLE_PEER_FAILED_VERIFICATION; + } + } + } + + if(result) + /* error already detected, pass through */ + ; + else if(!peer_CN) { + failf(data, + "SSL: unable to obtain common name from peer certificate"); + result = CURLE_PEER_FAILED_VERIFICATION; + } + else if(!Curl_cert_hostcheck((const char *)peer_CN, + peerlen, peer->hostname, hostlen)) { + failf(data, "SSL: certificate subject name '%s' does not match " + "target host name '%s'", peer_CN, peer->dispname); + result = CURLE_PEER_FAILED_VERIFICATION; + } + else { + infof(data, " common name: %s (matched)", peer_CN); + } + if(peer_CN) + OPENSSL_free(peer_CN); + } + + return result; +} + +#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ + !defined(OPENSSL_NO_OCSP) +static CURLcode verifystatus(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + int i, ocsp_status; +#if defined(OPENSSL_IS_AWSLC) + const uint8_t *status; +#else + unsigned char *status; +#endif + const unsigned char *p; + CURLcode result = CURLE_OK; + OCSP_RESPONSE *rsp = NULL; + OCSP_BASICRESP *br = NULL; + X509_STORE *st = NULL; + STACK_OF(X509) *ch = NULL; + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + X509 *cert; + OCSP_CERTID *id = NULL; + int cert_status, crl_reason; + ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; + int ret; + long len; + + DEBUGASSERT(backend); + + len = SSL_get_tlsext_status_ocsp_resp(backend->handle, &status); + + if(!status) { + failf(data, "No OCSP response received"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + p = status; + rsp = d2i_OCSP_RESPONSE(NULL, &p, len); + if(!rsp) { + failf(data, "Invalid OCSP response"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + ocsp_status = OCSP_response_status(rsp); + if(ocsp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + failf(data, "Invalid OCSP response status: %s (%d)", + OCSP_response_status_str(ocsp_status), ocsp_status); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + br = OCSP_response_get1_basic(rsp); + if(!br) { + failf(data, "Invalid OCSP response"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + ch = SSL_get_peer_cert_chain(backend->handle); + if(!ch) { + failf(data, "Could not get peer certificate chain"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + st = SSL_CTX_get_cert_store(backend->ctx); + +#if ((OPENSSL_VERSION_NUMBER <= 0x1000201fL) /* Fixed after 1.0.2a */ || \ + (defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER <= 0x2040200fL)) + /* The authorized responder cert in the OCSP response MUST be signed by the + peer cert's issuer (see RFC6960 section 4.2.2.2). If that's a root cert, + no problem, but if it's an intermediate cert OpenSSL has a bug where it + expects this issuer to be present in the chain embedded in the OCSP + response. So we add it if necessary. */ + + /* First make sure the peer cert chain includes both a peer and an issuer, + and the OCSP response contains a responder cert. */ + if(sk_X509_num(ch) >= 2 && sk_X509_num(br->certs) >= 1) { + X509 *responder = sk_X509_value(br->certs, sk_X509_num(br->certs) - 1); + + /* Find issuer of responder cert and add it to the OCSP response chain */ + for(i = 0; i < sk_X509_num(ch); i++) { + X509 *issuer = sk_X509_value(ch, i); + if(X509_check_issued(issuer, responder) == X509_V_OK) { + if(!OCSP_basic_add1_cert(br, issuer)) { + failf(data, "Could not add issuer cert to OCSP response"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + } + } + } +#endif + + if(OCSP_basic_verify(br, ch, st, 0) <= 0) { + failf(data, "OCSP response verification failed"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + /* Compute the certificate's ID */ + cert = SSL_get1_peer_certificate(backend->handle); + if(!cert) { + failf(data, "Error getting peer certificate"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + for(i = 0; i < (int)sk_X509_num(ch); i++) { + X509 *issuer = sk_X509_value(ch, i); + if(X509_check_issued(issuer, cert) == X509_V_OK) { + id = OCSP_cert_to_id(EVP_sha1(), cert, issuer); + break; + } + } + X509_free(cert); + + if(!id) { + failf(data, "Error computing OCSP ID"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + /* Find the single OCSP response corresponding to the certificate ID */ + ret = OCSP_resp_find_status(br, id, &cert_status, &crl_reason, &rev, + &thisupd, &nextupd); + OCSP_CERTID_free(id); + if(ret != 1) { + failf(data, "Could not find certificate ID in OCSP response"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + /* Validate the corresponding single OCSP response */ + if(!OCSP_check_validity(thisupd, nextupd, 300L, -1L)) { + failf(data, "OCSP response has expired"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + infof(data, "SSL certificate status: %s (%d)", + OCSP_cert_status_str(cert_status), cert_status); + + switch(cert_status) { + case V_OCSP_CERTSTATUS_GOOD: + break; + + case V_OCSP_CERTSTATUS_REVOKED: + result = CURLE_SSL_INVALIDCERTSTATUS; + failf(data, "SSL certificate revocation reason: %s (%d)", + OCSP_crl_reason_str(crl_reason), crl_reason); + goto end; + + case V_OCSP_CERTSTATUS_UNKNOWN: + default: + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + +end: + if(br) + OCSP_BASICRESP_free(br); + OCSP_RESPONSE_free(rsp); + + return result; +} +#endif + +#endif /* USE_OPENSSL */ + +/* The SSL_CTRL_SET_MSG_CALLBACK doesn't exist in ancient OpenSSL versions + and thus this cannot be done there. */ +#ifdef SSL_CTRL_SET_MSG_CALLBACK + +static const char *ssl_msg_type(int ssl_ver, int msg) +{ +#ifdef SSL2_VERSION_MAJOR + if(ssl_ver == SSL2_VERSION_MAJOR) { + switch(msg) { + case SSL2_MT_ERROR: + return "Error"; + case SSL2_MT_CLIENT_HELLO: + return "Client hello"; + case SSL2_MT_CLIENT_MASTER_KEY: + return "Client key"; + case SSL2_MT_CLIENT_FINISHED: + return "Client finished"; + case SSL2_MT_SERVER_HELLO: + return "Server hello"; + case SSL2_MT_SERVER_VERIFY: + return "Server verify"; + case SSL2_MT_SERVER_FINISHED: + return "Server finished"; + case SSL2_MT_REQUEST_CERTIFICATE: + return "Request CERT"; + case SSL2_MT_CLIENT_CERTIFICATE: + return "Client CERT"; + } + } + else +#endif + if(ssl_ver == SSL3_VERSION_MAJOR) { + switch(msg) { + case SSL3_MT_HELLO_REQUEST: + return "Hello request"; + case SSL3_MT_CLIENT_HELLO: + return "Client hello"; + case SSL3_MT_SERVER_HELLO: + return "Server hello"; +#ifdef SSL3_MT_NEWSESSION_TICKET + case SSL3_MT_NEWSESSION_TICKET: + return "Newsession Ticket"; +#endif + case SSL3_MT_CERTIFICATE: + return "Certificate"; + case SSL3_MT_SERVER_KEY_EXCHANGE: + return "Server key exchange"; + case SSL3_MT_CLIENT_KEY_EXCHANGE: + return "Client key exchange"; + case SSL3_MT_CERTIFICATE_REQUEST: + return "Request CERT"; + case SSL3_MT_SERVER_DONE: + return "Server finished"; + case SSL3_MT_CERTIFICATE_VERIFY: + return "CERT verify"; + case SSL3_MT_FINISHED: + return "Finished"; +#ifdef SSL3_MT_CERTIFICATE_STATUS + case SSL3_MT_CERTIFICATE_STATUS: + return "Certificate Status"; +#endif +#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS + case SSL3_MT_ENCRYPTED_EXTENSIONS: + return "Encrypted Extensions"; +#endif +#ifdef SSL3_MT_SUPPLEMENTAL_DATA + case SSL3_MT_SUPPLEMENTAL_DATA: + return "Supplemental data"; +#endif +#ifdef SSL3_MT_END_OF_EARLY_DATA + case SSL3_MT_END_OF_EARLY_DATA: + return "End of early data"; +#endif +#ifdef SSL3_MT_KEY_UPDATE + case SSL3_MT_KEY_UPDATE: + return "Key update"; +#endif +#ifdef SSL3_MT_NEXT_PROTO + case SSL3_MT_NEXT_PROTO: + return "Next protocol"; +#endif +#ifdef SSL3_MT_MESSAGE_HASH + case SSL3_MT_MESSAGE_HASH: + return "Message hash"; +#endif + } + } + return "Unknown"; +} + +static const char *tls_rt_type(int type) +{ + switch(type) { +#ifdef SSL3_RT_HEADER + case SSL3_RT_HEADER: + return "TLS header"; +#endif + case SSL3_RT_CHANGE_CIPHER_SPEC: + return "TLS change cipher"; + case SSL3_RT_ALERT: + return "TLS alert"; + case SSL3_RT_HANDSHAKE: + return "TLS handshake"; + case SSL3_RT_APPLICATION_DATA: + return "TLS app data"; + default: + return "TLS Unknown"; + } +} + +/* + * Our callback from the SSL/TLS layers. + */ +static void ossl_trace(int direction, int ssl_ver, int content_type, + const void *buf, size_t len, SSL *ssl, + void *userp) +{ + const char *verstr = "???"; + struct Curl_cfilter *cf = userp; + struct Curl_easy *data = NULL; + char unknown[32]; + + if(!cf) + return; + data = CF_DATA_CURRENT(cf); + if(!data || !data->set.fdebug || (direction && direction != 1)) + return; + + switch(ssl_ver) { +#ifdef SSL2_VERSION /* removed in recent versions */ + case SSL2_VERSION: + verstr = "SSLv2"; + break; +#endif +#ifdef SSL3_VERSION + case SSL3_VERSION: + verstr = "SSLv3"; + break; +#endif + case TLS1_VERSION: + verstr = "TLSv1.0"; + break; +#ifdef TLS1_1_VERSION + case TLS1_1_VERSION: + verstr = "TLSv1.1"; + break; +#endif +#ifdef TLS1_2_VERSION + case TLS1_2_VERSION: + verstr = "TLSv1.2"; + break; +#endif +#ifdef TLS1_3_VERSION + case TLS1_3_VERSION: + verstr = "TLSv1.3"; + break; +#endif + case 0: + break; + default: + msnprintf(unknown, sizeof(unknown), "(%x)", ssl_ver); + verstr = unknown; + break; + } + + /* Log progress for interesting records only (like Handshake or Alert), skip + * all raw record headers (content_type == SSL3_RT_HEADER or ssl_ver == 0). + * For TLS 1.3, skip notification of the decrypted inner Content-Type. + */ + if(ssl_ver +#ifdef SSL3_RT_HEADER + && content_type != SSL3_RT_HEADER +#endif +#ifdef SSL3_RT_INNER_CONTENT_TYPE + && content_type != SSL3_RT_INNER_CONTENT_TYPE +#endif + ) { + const char *msg_name, *tls_rt_name; + char ssl_buf[1024]; + int msg_type, txt_len; + + /* the info given when the version is zero is not that useful for us */ + + ssl_ver >>= 8; /* check the upper 8 bits only below */ + + /* SSLv2 doesn't seem to have TLS record-type headers, so OpenSSL + * always pass-up content-type as 0. But the interesting message-type + * is at 'buf[0]'. + */ + if(ssl_ver == SSL3_VERSION_MAJOR && content_type) + tls_rt_name = tls_rt_type(content_type); + else + tls_rt_name = ""; + + if(content_type == SSL3_RT_CHANGE_CIPHER_SPEC) { + msg_type = *(char *)buf; + msg_name = "Change cipher spec"; + } + else if(content_type == SSL3_RT_ALERT) { + msg_type = (((char *)buf)[0] << 8) + ((char *)buf)[1]; + msg_name = SSL_alert_desc_string_long(msg_type); + } + else { + msg_type = *(char *)buf; + msg_name = ssl_msg_type(ssl_ver, msg_type); + } + + txt_len = msnprintf(ssl_buf, sizeof(ssl_buf), + "%s (%s), %s, %s (%d):\n", + verstr, direction?"OUT":"IN", + tls_rt_name, msg_name, msg_type); + if(0 <= txt_len && (unsigned)txt_len < sizeof(ssl_buf)) { + Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len); + } + } + + Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT : + CURLINFO_SSL_DATA_IN, (char *)buf, len); + (void) ssl; +} +#endif + +#ifdef USE_OPENSSL +/* ====================================================== */ + +/* Check for OpenSSL 1.0.2 which has ALPN support. */ +#undef HAS_ALPN +#if OPENSSL_VERSION_NUMBER >= 0x10002000L \ + && !defined(OPENSSL_NO_TLSEXT) +# define HAS_ALPN 1 +#endif + +/* Check for OpenSSL 1.1.0 which has set_{min,max}_proto_version(). */ +#undef HAS_MODERN_SET_PROTO_VER +#if OPENSSL_VERSION_NUMBER >= 0x10100000L \ + && !(defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER < 0x20600000L) +# define HAS_MODERN_SET_PROTO_VER 1 +#endif + +#ifdef HAS_MODERN_SET_PROTO_VER +static CURLcode +ossl_set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + /* first, TLS min version... */ + long curl_ssl_version_min = conn_config->version; + long curl_ssl_version_max; + + /* convert curl min SSL version option to OpenSSL constant */ +#if (defined(OPENSSL_IS_BORINGSSL) || \ + defined(OPENSSL_IS_AWSLC) || \ + defined(LIBRESSL_VERSION_NUMBER)) + uint16_t ossl_ssl_version_min = 0; + uint16_t ossl_ssl_version_max = 0; +#else + long ossl_ssl_version_min = 0; + long ossl_ssl_version_max = 0; +#endif + switch(curl_ssl_version_min) { + case CURL_SSLVERSION_TLSv1: /* TLS 1.x */ + case CURL_SSLVERSION_TLSv1_0: + ossl_ssl_version_min = TLS1_VERSION; + break; + case CURL_SSLVERSION_TLSv1_1: + ossl_ssl_version_min = TLS1_1_VERSION; + break; + case CURL_SSLVERSION_TLSv1_2: + ossl_ssl_version_min = TLS1_2_VERSION; + break; + case CURL_SSLVERSION_TLSv1_3: +#ifdef TLS1_3_VERSION + ossl_ssl_version_min = TLS1_3_VERSION; + break; +#else + return CURLE_NOT_BUILT_IN; +#endif + } + + /* CURL_SSLVERSION_DEFAULT means that no option was selected. + We don't want to pass 0 to SSL_CTX_set_min_proto_version as + it would enable all versions down to the lowest supported by + the library. + So we skip this, and stay with the library default + */ + if(curl_ssl_version_min != CURL_SSLVERSION_DEFAULT) { + if(!SSL_CTX_set_min_proto_version(ctx, ossl_ssl_version_min)) { + return CURLE_SSL_CONNECT_ERROR; + } + } + + /* ... then, TLS max version */ + curl_ssl_version_max = conn_config->version_max; + + /* convert curl max SSL version option to OpenSSL constant */ + switch(curl_ssl_version_max) { + case CURL_SSLVERSION_MAX_TLSv1_0: + ossl_ssl_version_max = TLS1_VERSION; + break; + case CURL_SSLVERSION_MAX_TLSv1_1: + ossl_ssl_version_max = TLS1_1_VERSION; + break; + case CURL_SSLVERSION_MAX_TLSv1_2: + ossl_ssl_version_max = TLS1_2_VERSION; + break; +#ifdef TLS1_3_VERSION + case CURL_SSLVERSION_MAX_TLSv1_3: + ossl_ssl_version_max = TLS1_3_VERSION; + break; +#endif + case CURL_SSLVERSION_MAX_NONE: /* none selected */ + case CURL_SSLVERSION_MAX_DEFAULT: /* max selected */ + default: + /* SSL_CTX_set_max_proto_version states that: + setting the maximum to 0 will enable + protocol versions up to the highest version + supported by the library */ + ossl_ssl_version_max = 0; + break; + } + + if(!SSL_CTX_set_max_proto_version(ctx, ossl_ssl_version_max)) { + return CURLE_SSL_CONNECT_ERROR; + } + + return CURLE_OK; +} +#endif /* HAS_MODERN_SET_PROTO_VER */ + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +typedef uint32_t ctx_option_t; +#elif OPENSSL_VERSION_NUMBER >= 0x30000000L +typedef uint64_t ctx_option_t; +#else +typedef long ctx_option_t; +#endif + +#if !defined(HAS_MODERN_SET_PROTO_VER) +static CURLcode +ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + long ssl_version = conn_config->version; + long ssl_version_max = conn_config->version_max; + + (void) data; /* In case it's unused. */ + + switch(ssl_version) { + case CURL_SSLVERSION_TLSv1_3: +#ifdef TLS1_3_VERSION + { + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + DEBUGASSERT(backend); + SSL_CTX_set_max_proto_version(backend->ctx, TLS1_3_VERSION); + *ctx_options |= SSL_OP_NO_TLSv1_2; + } +#else + (void)ctx_options; + failf(data, OSSL_PACKAGE " was built without TLS 1.3 support"); + return CURLE_NOT_BUILT_IN; +#endif + /* FALLTHROUGH */ + case CURL_SSLVERSION_TLSv1_2: +#if OPENSSL_VERSION_NUMBER >= 0x1000100FL + *ctx_options |= SSL_OP_NO_TLSv1_1; +#else + failf(data, OSSL_PACKAGE " was built without TLS 1.2 support"); + return CURLE_NOT_BUILT_IN; +#endif + /* FALLTHROUGH */ + case CURL_SSLVERSION_TLSv1_1: +#if OPENSSL_VERSION_NUMBER >= 0x1000100FL + *ctx_options |= SSL_OP_NO_TLSv1; +#else + failf(data, OSSL_PACKAGE " was built without TLS 1.1 support"); + return CURLE_NOT_BUILT_IN; +#endif + /* FALLTHROUGH */ + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1: + break; + } + + switch(ssl_version_max) { + case CURL_SSLVERSION_MAX_TLSv1_0: +#if OPENSSL_VERSION_NUMBER >= 0x1000100FL + *ctx_options |= SSL_OP_NO_TLSv1_1; +#endif + /* FALLTHROUGH */ + case CURL_SSLVERSION_MAX_TLSv1_1: +#if OPENSSL_VERSION_NUMBER >= 0x1000100FL + *ctx_options |= SSL_OP_NO_TLSv1_2; +#endif + /* FALLTHROUGH */ + case CURL_SSLVERSION_MAX_TLSv1_2: +#ifdef TLS1_3_VERSION + *ctx_options |= SSL_OP_NO_TLSv1_3; +#endif + break; + case CURL_SSLVERSION_MAX_TLSv1_3: +#ifdef TLS1_3_VERSION + break; +#else + failf(data, OSSL_PACKAGE " was built without TLS 1.3 support"); + return CURLE_NOT_BUILT_IN; +#endif + } + return CURLE_OK; +} +#endif /* ! HAS_MODERN_SET_PROTO_VER */ + +/* The "new session" callback must return zero if the session can be removed + * or non-zero if the session has been put into the session cache. + */ +static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +{ + int res = 0; + struct Curl_easy *data; + struct Curl_cfilter *cf; + const struct ssl_config_data *config; + struct ssl_connect_data *connssl; + bool isproxy; + + cf = (struct Curl_cfilter*) SSL_get_app_data(ssl); + connssl = cf? cf->ctx : NULL; + data = connssl? CF_DATA_CURRENT(cf) : NULL; + /* The sockindex has been stored as a pointer to an array element */ + if(!cf || !data) + return 0; + + isproxy = Curl_ssl_cf_is_proxy(cf); + + config = Curl_ssl_cf_get_config(cf, data); + if(config->primary.sessionid) { + bool incache; + bool added = FALSE; + void *old_ssl_sessionid = NULL; + + Curl_ssl_sessionid_lock(data); + if(isproxy) + incache = FALSE; + else + incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)); + if(incache) { + if(old_ssl_sessionid != ssl_sessionid) { + infof(data, "old SSL session ID is stale, removing"); + Curl_ssl_delsessionid(data, old_ssl_sessionid); + incache = FALSE; + } + } + + if(!incache) { + if(!Curl_ssl_addsessionid(cf, data, ssl_sessionid, + 0 /* unknown size */, &added)) { + if(added) { + /* the session has been put into the session cache */ + res = 1; + } + } + else + failf(data, "failed to store ssl session"); + } + Curl_ssl_sessionid_unlock(data); + } + + return res; +} + +static CURLcode load_cacert_from_memory(X509_STORE *store, + const struct curl_blob *ca_info_blob) +{ + /* these need to be freed at the end */ + BIO *cbio = NULL; + STACK_OF(X509_INFO) *inf = NULL; + + /* everything else is just a reference */ + int i, count = 0; + X509_INFO *itmp = NULL; + + if(ca_info_blob->len > (size_t)INT_MAX) + return CURLE_SSL_CACERT_BADFILE; + + cbio = BIO_new_mem_buf(ca_info_blob->data, (int)ca_info_blob->len); + if(!cbio) + return CURLE_OUT_OF_MEMORY; + + inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL); + if(!inf) { + BIO_free(cbio); + return CURLE_SSL_CACERT_BADFILE; + } + + /* add each entry from PEM file to x509_store */ + for(i = 0; i < (int)sk_X509_INFO_num(inf); ++i) { + itmp = sk_X509_INFO_value(inf, i); + if(itmp->x509) { + if(X509_STORE_add_cert(store, itmp->x509)) { + ++count; + } + else { + /* set count to 0 to return an error */ + count = 0; + break; + } + } + if(itmp->crl) { + if(X509_STORE_add_crl(store, itmp->crl)) { + ++count; + } + else { + /* set count to 0 to return an error */ + count = 0; + break; + } + } + } + + sk_X509_INFO_pop_free(inf, X509_INFO_free); + BIO_free(cbio); + + /* if we didn't end up importing anything, treat that as an error */ + return (count > 0) ? CURLE_OK : CURLE_SSL_CACERT_BADFILE; +} + +#if defined(USE_WIN32_CRYPTO) +static CURLcode import_windows_cert_store(struct Curl_easy *data, + const char *name, + X509_STORE *store, + bool *imported) +{ + CURLcode result = CURLE_OK; + HCERTSTORE hStore; + + *imported = false; + + hStore = CertOpenSystemStoreA(0, name); + if(hStore) { + PCCERT_CONTEXT pContext = NULL; + /* The array of enhanced key usage OIDs will vary per certificate and + is declared outside of the loop so that rather than malloc/free each + iteration we can grow it with realloc, when necessary. */ + CERT_ENHKEY_USAGE *enhkey_usage = NULL; + DWORD enhkey_usage_size = 0; + + /* This loop makes a best effort to import all valid certificates from + the MS root store. If a certificate cannot be imported it is + skipped. 'result' is used to store only hard-fail conditions (such + as out of memory) that cause an early break. */ + result = CURLE_OK; + for(;;) { + X509 *x509; + FILETIME now; + BYTE key_usage[2]; + DWORD req_size; + const unsigned char *encoded_cert; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + char cert_name[256]; +#endif + + pContext = CertEnumCertificatesInStore(hStore, pContext); + if(!pContext) + break; + +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, + NULL, cert_name, sizeof(cert_name))) { + strcpy(cert_name, "Unknown"); + } + infof(data, "SSL: Checking cert \"%s\"", cert_name); +#endif + encoded_cert = (const unsigned char *)pContext->pbCertEncoded; + if(!encoded_cert) + continue; + + GetSystemTimeAsFileTime(&now); + if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 || + CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0) + continue; + + /* If key usage exists check for signing attribute */ + if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType, + pContext->pCertInfo, + key_usage, sizeof(key_usage))) { + if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE)) + continue; + } + else if(GetLastError()) + continue; + + /* If enhanced key usage exists check for server auth attribute. + * + * Note "In a Microsoft environment, a certificate might also have + * EKU extended properties that specify valid uses for the + * certificate." The call below checks both, and behavior varies + * depending on what is found. For more details see + * CertGetEnhancedKeyUsage doc. + */ + if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) { + if(req_size && req_size > enhkey_usage_size) { + void *tmp = realloc(enhkey_usage, req_size); + + if(!tmp) { + failf(data, "SSL: Out of memory allocating for OID list"); + result = CURLE_OUT_OF_MEMORY; + break; + } + + enhkey_usage = (CERT_ENHKEY_USAGE *)tmp; + enhkey_usage_size = req_size; + } + + if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) { + if(!enhkey_usage->cUsageIdentifier) { + /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate + is good for all uses. If it returns zero, the certificate + has no valid uses." */ + if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND) + continue; + } + else { + DWORD i; + bool found = false; + + for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) { + if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */, + enhkey_usage->rgpszUsageIdentifier[i])) { + found = true; + break; + } + } + + if(!found) + continue; + } + } + else + continue; + } + else + continue; + + x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if(!x509) + continue; + + /* Try to import the certificate. This may fail for legitimate + reasons such as duplicate certificate, which is allowed by MS but + not OpenSSL. */ + if(X509_STORE_add_cert(store, x509) == 1) { +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + infof(data, "SSL: Imported cert \"%s\"", cert_name); +#endif + *imported = true; + } + X509_free(x509); + } + + free(enhkey_usage); + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + if(result) + return result; + } + + return result; +} +#endif + +static CURLcode populate_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + 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; + X509_LOOKUP *lookup = NULL; + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + const char * const ssl_cafile = + /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ + (ca_info_blob ? NULL : conn_config->CAfile); + const char * const ssl_capath = conn_config->CApath; + const char * const ssl_crlfile = ssl_config->primary.CRLfile; + const bool verifypeer = conn_config->verifypeer; + bool imported_native_ca = false; + bool imported_ca_info_blob = false; + + if(!store) + return CURLE_OUT_OF_MEMORY; + + if(verifypeer) { +#if defined(USE_WIN32_CRYPTO) + /* Import certificates from the Windows root certificate store if + requested. + https://stackoverflow.com/questions/9507184/ + https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 + https://datatracker.ietf.org/doc/html/rfc5280 */ + if(ssl_config->native_ca_store) { + const char *storeNames[] = { + "ROOT", /* Trusted Root Certification Authorities */ + "CA" /* Intermediate Certification Authorities */ + }; + size_t i; + for(i = 0; i < ARRAYSIZE(storeNames); ++i) { + bool imported = false; + result = import_windows_cert_store(data, storeNames[i], store, + &imported); + if(result) + return result; + if(imported) { + infof(data, "successfully imported Windows %s store", storeNames[i]); + imported_native_ca = true; + } + else + infof(data, "error importing Windows %s store, continuing anyway", + storeNames[i]); + } + } +#endif + if(ca_info_blob) { + result = load_cacert_from_memory(store, ca_info_blob); + if(result) { + failf(data, "error importing CA certificate blob"); + return result; + } + else { + imported_ca_info_blob = true; + infof(data, "successfully imported CA certificate blob"); + } + } + + if(ssl_cafile || ssl_capath) { +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) + /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ + if(ssl_cafile && !X509_STORE_load_file(store, ssl_cafile)) { + if(!imported_native_ca && !imported_ca_info_blob) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate file: %s", ssl_cafile); + return CURLE_SSL_CACERT_BADFILE; + } + else + infof(data, "error setting certificate file, continuing anyway"); + } + if(ssl_capath && !X509_STORE_load_path(store, ssl_capath)) { + if(!imported_native_ca && !imported_ca_info_blob) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate path: %s", ssl_capath); + return CURLE_SSL_CACERT_BADFILE; + } + else + infof(data, "error setting certificate path, continuing anyway"); + } +#else + /* tell OpenSSL where to find CA certificates that are used to verify the + server's certificate. */ + if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) { + if(!imported_native_ca && !imported_ca_info_blob) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate verify locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + return CURLE_SSL_CACERT_BADFILE; + } + else { + infof(data, "error setting certificate verify locations," + " continuing anyway"); + } + } +#endif + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + } + +#ifdef CURL_CA_FALLBACK + if(!ssl_cafile && !ssl_capath && + !imported_native_ca && !imported_ca_info_blob) { + /* verifying the peer without any CA certificates won't + work so use openssl's built-in default as fallback */ + X509_STORE_set_default_paths(store); + } +#endif + } + + if(ssl_crlfile) { + /* tell OpenSSL where to find CRL file that is used to check certificate + * revocation */ + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + if(!lookup || + (!X509_load_crl_file(lookup, ssl_crlfile, X509_FILETYPE_PEM)) ) { + failf(data, "error loading CRL file: %s", ssl_crlfile); + return CURLE_SSL_CRL_BADFILE; + } + /* Everything is fine. */ + infof(data, "successfully loaded CRL file:"); + X509_STORE_set_flags(store, + X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); + + infof(data, " CRLfile: %s", ssl_crlfile); + } + + if(verifypeer) { + /* Try building a chain using issuers in the trusted store first to avoid + problems with server-sent legacy intermediates. Newer versions of + OpenSSL do alternate chain checking by default but we do not know how to + determine that in a reliable manner. + https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest + */ +#if defined(X509_V_FLAG_TRUSTED_FIRST) + X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST); +#endif +#ifdef X509_V_FLAG_PARTIAL_CHAIN + if(!ssl_config->no_partialchain && !ssl_crlfile) { + /* Have intermediate certificates in the trust store be treated as + trust-anchors, in the same way as self-signed root CA certificates + are. This allows users to verify servers using the intermediate cert + only, 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. + */ + X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN); + } +#endif + } + + return result; +} + +#if defined(HAVE_SSL_X509_STORE_SHARE) +static bool cached_x509_store_expired(const struct Curl_easy *data, + const struct multi_ssl_backend_data *mb) +{ + const struct ssl_general_config *cfg = &data->set.general_ssl; + struct curltime now = Curl_now(); + timediff_t elapsed_ms = Curl_timediff(now, mb->time); + timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + + if(timeout_ms < 0) + return false; + + return elapsed_ms >= timeout_ms; +} + +static bool cached_x509_store_different( + struct Curl_cfilter *cf, + const struct multi_ssl_backend_data *mb) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!mb->CAfile || !conn_config->CAfile) + return mb->CAfile != conn_config->CAfile; + + return strcmp(mb->CAfile, conn_config->CAfile); +} + +static X509_STORE *get_cached_x509_store(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + X509_STORE *store = NULL; + + DEBUGASSERT(multi); + if(multi && + multi->ssl_backend_data && + multi->ssl_backend_data->store && + !cached_x509_store_expired(data, multi->ssl_backend_data) && + !cached_x509_store_different(cf, multi->ssl_backend_data)) { + store = multi->ssl_backend_data->store; + } + + return store; +} + +static void set_cached_x509_store(struct Curl_cfilter *cf, + const struct Curl_easy *data, + X509_STORE *store) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + struct multi_ssl_backend_data *mbackend; + + DEBUGASSERT(multi); + if(!multi) + return; + + if(!multi->ssl_backend_data) { + multi->ssl_backend_data = calloc(1, sizeof(struct multi_ssl_backend_data)); + if(!multi->ssl_backend_data) + return; + } + + mbackend = multi->ssl_backend_data; + + if(X509_STORE_up_ref(store)) { + char *CAfile = NULL; + + if(conn_config->CAfile) { + CAfile = strdup(conn_config->CAfile); + if(!CAfile) { + X509_STORE_free(store); + return; + } + } + + if(mbackend->store) { + X509_STORE_free(mbackend->store); + free(mbackend->CAfile); + } + + mbackend->time = Curl_now(); + mbackend->store = store; + mbackend->CAfile = CAfile; + } +} + +CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + SSL_CTX *ssl_ctx) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + CURLcode result = CURLE_OK; + X509_STORE *cached_store; + bool cache_criteria_met; + + /* Consider the X509 store cacheable if it comes exclusively from a CAfile, + or no source is provided and we are falling back to openssl's built-in + default. */ + cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) && + conn_config->verifypeer && + !conn_config->CApath && + !conn_config->ca_info_blob && + !ssl_config->primary.CRLfile && + !ssl_config->native_ca_store; + + cached_store = get_cached_x509_store(cf, data); + if(cached_store && cache_criteria_met && X509_STORE_up_ref(cached_store)) { + SSL_CTX_set_cert_store(ssl_ctx, cached_store); + } + else { + X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx); + + result = populate_x509_store(cf, data, store); + if(result == CURLE_OK && cache_criteria_met) { + set_cached_x509_store(cf, data, store); + } + } + + return result; +} +#else /* HAVE_SSL_X509_STORE_SHARE */ +CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + SSL_CTX *ssl_ctx) +{ + X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx); + + return populate_x509_store(cf, data, store); +} +#endif /* HAVE_SSL_X509_STORE_SHARE */ + +static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + char *ciphers; + SSL_METHOD_QUAL SSL_METHOD *req_method = NULL; + struct ssl_connect_data *connssl = cf->ctx; + ctx_option_t ctx_options = 0; + void *ssl_sessionid = NULL; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + BIO *bio; + const long int ssl_version = conn_config->version; + char * const ssl_cert = ssl_config->primary.clientcert; + const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; + const char * const ssl_cert_type = ssl_config->cert_type; + const bool verifypeer = conn_config->verifypeer; + char error_buffer[256]; + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + + DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + DEBUGASSERT(backend); + + /* Make funny stuff to get random input */ + result = ossl_seed(data); + if(result) + return result; + + ssl_config->certverifyresult = !X509_V_OK; + + /* check to see if we've been told to use an explicit SSL/TLS version */ + + switch(ssl_version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + case CURL_SSLVERSION_TLSv1_3: + /* it will be handled later with the context options */ +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + req_method = TLS_client_method(); +#else + req_method = SSLv23_client_method(); +#endif + break; + case CURL_SSLVERSION_SSLv2: + failf(data, "No SSLv2 support"); + return CURLE_NOT_BUILT_IN; + case CURL_SSLVERSION_SSLv3: + failf(data, "No SSLv3 support"); + return CURLE_NOT_BUILT_IN; + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(backend->ctx) { + /* This happens when an error was encountered before in this + * step and we are called to do it again. Get rid of any leftover + * from the previous call. */ + ossl_close(cf, data); + } + backend->ctx = SSL_CTX_new(req_method); + + if(!backend->ctx) { + failf(data, "SSL: couldn't create a context: %s", + ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer))); + return CURLE_OUT_OF_MEMORY; + } + +#ifdef SSL_MODE_RELEASE_BUFFERS + SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS); +#endif + +#ifdef SSL_CTRL_SET_MSG_CALLBACK + if(data->set.fdebug && data->set.verbose) { + /* the SSL trace callback is only used for verbose logging */ + SSL_CTX_set_msg_callback(backend->ctx, ossl_trace); + SSL_CTX_set_msg_callback_arg(backend->ctx, cf); + } +#endif + + /* OpenSSL contains code to work around lots of bugs and flaws in various + SSL-implementations. SSL_CTX_set_options() is used to enabled those + work-arounds. The man page for this option states that SSL_OP_ALL enables + all the work-arounds and that "It is usually safe to use SSL_OP_ALL to + enable the bug workaround options if compatibility with somewhat broken + implementations is desired." + + The "-no_ticket" option was introduced in OpenSSL 0.9.8j. It's a flag to + disable "rfc4507bis session ticket support". rfc4507bis was later turned + into the proper RFC5077: https://datatracker.ietf.org/doc/html/rfc5077 + + The enabled extension concerns the session management. I wonder how often + libcurl stops a connection and then resumes a TLS session. Also, sending + the session data is some overhead. I suggest that you just use your + proposed patch (which explicitly disables TICKET). + + If someone writes an application with libcurl and OpenSSL who wants to + enable the feature, one can do this in the SSL callback. + + SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper + interoperability with web server Netscape Enterprise Server 2.0.1 which + was released back in 1996. + + Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has + become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate + CVE-2010-4180 when using previous OpenSSL versions we no longer enable + this option regardless of OpenSSL version and SSL_OP_ALL definition. + + OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability + (https://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to + SSL_OP_ALL that _disables_ that work-around despite the fact that + SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to + keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit + must not be set. + */ + + ctx_options = SSL_OP_ALL; + +#ifdef SSL_OP_NO_TICKET + ctx_options |= SSL_OP_NO_TICKET; +#endif + +#ifdef SSL_OP_NO_COMPRESSION + ctx_options |= SSL_OP_NO_COMPRESSION; +#endif + +#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG + /* mitigate CVE-2010-4180 */ + ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; +#endif + +#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS + /* unless the user explicitly asks to allow the protocol vulnerability we + use the work-around */ + if(!ssl_config->enable_beast) + ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; +#endif + + switch(ssl_version) { + case CURL_SSLVERSION_SSLv2: + case CURL_SSLVERSION_SSLv3: + return CURLE_NOT_BUILT_IN; + + /* "--tlsv<x.y>" options mean TLS >= version <x.y> */ + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: /* TLS >= version 1.0 */ + case CURL_SSLVERSION_TLSv1_0: /* TLS >= version 1.0 */ + case CURL_SSLVERSION_TLSv1_1: /* TLS >= version 1.1 */ + case CURL_SSLVERSION_TLSv1_2: /* TLS >= version 1.2 */ + case CURL_SSLVERSION_TLSv1_3: /* TLS >= version 1.3 */ + /* asking for any TLS version as the minimum, means no SSL versions + allowed */ + ctx_options |= SSL_OP_NO_SSLv2; + ctx_options |= SSL_OP_NO_SSLv3; + +#if HAS_MODERN_SET_PROTO_VER /* 1.1.0 */ + result = ossl_set_ssl_version_min_max(cf, backend->ctx); +#else + result = ossl_set_ssl_version_min_max_legacy(&ctx_options, cf, data); +#endif + if(result != CURLE_OK) + return result; + break; + + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + + SSL_CTX_set_options(backend->ctx, ctx_options); + +#ifdef HAS_ALPN + if(connssl->alpn) { + struct alpn_proto_buf proto; + + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result || + SSL_CTX_set_alpn_protos(backend->ctx, proto.data, proto.len)) { + failf(data, "Error setting ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } +#endif + + if(ssl_cert || ssl_cert_blob || ssl_cert_type) { + if(!result && + !cert_stuff(data, backend->ctx, + ssl_cert, ssl_cert_blob, ssl_cert_type, + ssl_config->key, ssl_config->key_blob, + ssl_config->key_type, ssl_config->key_passwd)) + result = CURLE_SSL_CERTPROBLEM; + if(result) + /* failf() is already done in cert_stuff() */ + return result; + } + + ciphers = conn_config->cipher_list; + if(!ciphers) + ciphers = (char *)DEFAULT_CIPHER_SELECTION; + if(ciphers) { + if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) { + failf(data, "failed setting cipher list: %s", ciphers); + return CURLE_SSL_CIPHER; + } + infof(data, "Cipher selection: %s", ciphers); + } + +#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES + { + char *ciphers13 = conn_config->cipher_list13; + if(ciphers13) { + if(!SSL_CTX_set_ciphersuites(backend->ctx, ciphers13)) { + failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13); + return CURLE_SSL_CIPHER; + } + infof(data, "TLS 1.3 cipher selection: %s", ciphers13); + } + } +#endif + +#ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH + /* OpenSSL 1.1.1 requires clients to opt-in for PHA */ + SSL_CTX_set_post_handshake_auth(backend->ctx, 1); +#endif + +#ifdef HAVE_SSL_CTX_SET_EC_CURVES + { + char *curves = conn_config->curves; + if(curves) { + if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) { + failf(data, "failed setting curves list: '%s'", curves); + return CURLE_SSL_CIPHER; + } + } + } +#endif + +#ifdef USE_OPENSSL_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(backend->ctx, ssl_username)) { + failf(data, "Unable to set SRP user name"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!SSL_CTX_set_srp_password(backend->ctx, ssl_password)) { + failf(data, "failed setting SRP password"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!conn_config->cipher_list) { + infof(data, "Setting cipher list SRP"); + + if(!SSL_CTX_set_cipher_list(backend->ctx, "SRP")) { + failf(data, "failed setting SRP cipher list"); + return CURLE_SSL_CIPHER; + } + } + } +#endif + + /* OpenSSL always tries to verify the peer, this only says whether it should + * fail to connect if the verification fails, or if it should continue + * anyway. In the latter case the result of the verification is checked with + * SSL_get_verify_result() below. */ + SSL_CTX_set_verify(backend->ctx, + verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); + + /* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */ +#ifdef HAVE_KEYLOG_CALLBACK + if(Curl_tls_keylog_enabled()) { + SSL_CTX_set_keylog_callback(backend->ctx, ossl_keylog_callback); + } +#endif + + /* Enable the session cache because it's a prerequisite for the "new session" + * callback. Use the "external storage" mode to prevent OpenSSL from creating + * an internal session cache. + */ + SSL_CTX_set_session_cache_mode(backend->ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(backend->ctx, ossl_new_session_cb); + + /* give application a chance to interfere with SSL set up. */ + if(data->set.ssl.fsslctx) { + /* When a user callback is installed to modify the SSL_CTX, + * we need to do the full initialization before calling it. + * See: #11800 */ + if(!backend->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, backend->ctx); + if(result) + return result; + backend->x509_store_setup = TRUE; + } + Curl_set_in_callback(data, true); + result = (*data->set.ssl.fsslctx)(data, backend->ctx, + data->set.ssl.fsslctxp); + Curl_set_in_callback(data, false); + if(result) { + failf(data, "error signaled by ssl ctx callback"); + return result; + } + } + + /* Let's make an SSL structure */ + if(backend->handle) + SSL_free(backend->handle); + backend->handle = SSL_new(backend->ctx); + if(!backend->handle) { + failf(data, "SSL: couldn't create a context (handle)"); + return CURLE_OUT_OF_MEMORY; + } + + SSL_set_app_data(backend->handle, cf); + +#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ + !defined(OPENSSL_NO_OCSP) + if(conn_config->verifystatus) + SSL_set_tlsext_status_type(backend->handle, TLSEXT_STATUSTYPE_ocsp); +#endif + +#if (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)) && \ + defined(ALLOW_RENEG) + SSL_set_renegotiate_mode(backend->handle, ssl_renegotiate_freely); +#endif + + SSL_set_connect_state(backend->handle); + + backend->server_cert = 0x0; +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + if(connssl->peer.sni) { + if(!SSL_set_tlsext_host_name(backend->handle, connssl->peer.sni)) { + failf(data, "Failed set SNI"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif + + SSL_set_app_data(backend->handle, cf); + + connssl->reused_session = FALSE; + if(ssl_config->primary.sessionid) { + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)) { + /* we got a session id, use it! */ + if(!SSL_set_session(backend->handle, ssl_sessionid)) { + Curl_ssl_sessionid_unlock(data); + failf(data, "SSL: SSL_set_session failed: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + return CURLE_SSL_CONNECT_ERROR; + } + /* Informational message */ + infof(data, "SSL reusing session ID"); + connssl->reused_session = TRUE; + } + Curl_ssl_sessionid_unlock(data); + } + + backend->bio_method = ossl_bio_cf_method_create(); + if(!backend->bio_method) + return CURLE_OUT_OF_MEMORY; + bio = BIO_new(backend->bio_method); + if(!bio) + return CURLE_OUT_OF_MEMORY; + + BIO_set_data(bio, cf); +#ifdef HAVE_SSL_SET0_WBIO + /* with OpenSSL v1.1.1 we get an alternative to SSL_set_bio() that works + * without backward compat quirks. Every call takes one reference, so we + * up it and pass. SSL* then owns it and will free. + * We check on the function in configure, since libressl and friends + * each have their own versions to add support for this. */ + BIO_up_ref(bio); + SSL_set0_rbio(backend->handle, bio); + SSL_set0_wbio(backend->handle, bio); +#else + SSL_set_bio(backend->handle, bio, bio); +#endif + connssl->connecting_state = ssl_connect_2; + + return CURLE_OK; +} + +static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + int err; + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + DEBUGASSERT(ssl_connect_2 == connssl->connecting_state + || ssl_connect_2_reading == connssl->connecting_state + || ssl_connect_2_writing == connssl->connecting_state); + DEBUGASSERT(backend); + + ERR_clear_error(); + + err = SSL_connect(backend->handle); + + if(!backend->x509_store_setup) { + /* After having send off the ClientHello, we prepare the x509 + * store to verify the coming certificate from the server */ + CURLcode result = Curl_ssl_setup_x509_store(cf, data, backend->ctx); + if(result) + return result; + backend->x509_store_setup = TRUE; + } + +#ifndef HAVE_KEYLOG_CALLBACK + if(Curl_tls_keylog_enabled()) { + /* If key logging is enabled, wait for the handshake to complete and then + * proceed with logging secrets (for TLS 1.2 or older). + */ + ossl_log_tls12_secret(backend->handle, &backend->keylog_done); + } +#endif + + /* 1 is fine + 0 is "not successful but was shut down controlled" + <0 is "handshake was not successful, because a fatal error occurred" */ + if(1 != err) { + int detail = SSL_get_error(backend->handle, err); + + if(SSL_ERROR_WANT_READ == detail) { + connssl->connecting_state = ssl_connect_2_reading; + return CURLE_OK; + } + if(SSL_ERROR_WANT_WRITE == detail) { + connssl->connecting_state = ssl_connect_2_writing; + return CURLE_OK; + } +#ifdef SSL_ERROR_WANT_ASYNC + if(SSL_ERROR_WANT_ASYNC == detail) { + connssl->connecting_state = ssl_connect_2; + return CURLE_OK; + } +#endif +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + if(SSL_ERROR_WANT_RETRY_VERIFY == detail) { + connssl->connecting_state = ssl_connect_2; + return CURLE_OK; + } +#endif + if(backend->io_result == CURLE_AGAIN) { + return CURLE_OK; + } + else { + /* untreated error */ + sslerr_t errdetail; + char error_buffer[256]=""; + CURLcode result; + long lerr; + int lib; + int reason; + + /* the connection failed, we're not waiting for anything else. */ + connssl->connecting_state = ssl_connect_2; + + /* Get the earliest error code from the thread's error queue and remove + the entry. */ + errdetail = ERR_get_error(); + + /* Extract which lib and reason */ + lib = ERR_GET_LIB(errdetail); + reason = ERR_GET_REASON(errdetail); + + if((lib == ERR_LIB_SSL) && + ((reason == SSL_R_CERTIFICATE_VERIFY_FAILED) || + (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) { + result = CURLE_PEER_FAILED_VERIFICATION; + + lerr = SSL_get_verify_result(backend->handle); + if(lerr != X509_V_OK) { + ssl_config->certverifyresult = lerr; + msnprintf(error_buffer, sizeof(error_buffer), + "SSL certificate problem: %s", + X509_verify_cert_error_string(lerr)); + } + else + /* strcpy() is fine here as long as the string fits within + error_buffer */ + strcpy(error_buffer, "SSL certificate verification failed"); + } +#if defined(SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED) + /* SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED is only available on + OpenSSL version above v1.1.1, not LibreSSL, BoringSSL, or AWS-LC */ + else if((lib == ERR_LIB_SSL) && + (reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED)) { + /* If client certificate is required, communicate the + error to client */ + result = CURLE_SSL_CLIENTCERT; + ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); + } +#endif + else { + result = CURLE_SSL_CONNECT_ERROR; + ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); + } + + /* detail is already set to the SSL error above */ + + /* If we e.g. use SSLv2 request-method and the server doesn't like us + * (RST connection, etc.), OpenSSL gives no explanation whatsoever and + * the SO_ERROR is also lost. + */ + if(CURLE_SSL_CONNECT_ERROR == result && errdetail == 0) { + char extramsg[80]=""; + int sockerr = SOCKERRNO; + + if(sockerr && detail == SSL_ERROR_SYSCALL) + Curl_strerror(sockerr, extramsg, sizeof(extramsg)); + failf(data, OSSL_PACKAGE " SSL_connect: %s in connection to %s:%d ", + extramsg[0] ? extramsg : SSL_ERROR_to_str(detail), + connssl->peer.hostname, connssl->port); + return result; + } + + /* Could be a CERT problem */ + failf(data, "%s", error_buffer); + + return result; + } + } + else { + int psigtype_nid = NID_undef; + const char *negotiated_group_name = NULL; + + /* we connected fine, we're not waiting for anything else. */ + connssl->connecting_state = ssl_connect_3; + +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) + SSL_get_peer_signature_type_nid(backend->handle, &psigtype_nid); +#if (OPENSSL_VERSION_NUMBER >= 0x30200000L) + negotiated_group_name = SSL_get0_group_name(backend->handle); +#else + negotiated_group_name = + OBJ_nid2sn(SSL_get_negotiated_group(backend->handle) & 0x0000FFFF); +#endif +#endif + + /* Informational message */ + infof(data, "SSL connection using %s / %s / %s / %s", + SSL_get_version(backend->handle), + SSL_get_cipher(backend->handle), + negotiated_group_name? negotiated_group_name : "[blank]", + OBJ_nid2sn(psigtype_nid)); + +#ifdef HAS_ALPN + /* Sets data and len to negotiated protocol, len is 0 if no protocol was + * negotiated + */ + if(connssl->alpn) { + const unsigned char *neg_protocol; + unsigned int len; + SSL_get0_alpn_selected(backend->handle, &neg_protocol, &len); + + return Curl_alpn_set_negotiated(cf, data, neg_protocol, len); + } +#endif + + return CURLE_OK; + } +} + +/* + * Heavily modified from: + * https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#OpenSSL + */ +static CURLcode ossl_pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, + const char *pinnedpubkey) +{ + /* Scratch */ + int len1 = 0, len2 = 0; + unsigned char *buff1 = NULL, *temp = NULL; + + /* Result is returned to caller */ + CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; + + /* if a path wasn't specified, don't pin */ + if(!pinnedpubkey) + return CURLE_OK; + + if(!cert) + return result; + + do { + /* Begin Gyrations to get the subjectPublicKeyInfo */ + /* Thanks to Viktor Dukhovni on the OpenSSL mailing list */ + + /* https://groups.google.com/group/mailing.openssl.users/browse_thread + /thread/d61858dae102c6c7 */ + len1 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL); + if(len1 < 1) + break; /* failed */ + + buff1 = temp = malloc(len1); + if(!buff1) + break; /* failed */ + + /* https://www.openssl.org/docs/crypto/d2i_X509.html */ + len2 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp); + + /* + * These checks are verifying we got back the same values as when we + * sized the buffer. It's pretty weak since they should always be the + * same. But it gives us something to test. + */ + if((len1 != len2) || !temp || ((temp - buff1) != len1)) + break; /* failed */ + + /* End Gyrations */ + + /* The one good exit point */ + result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1); + } while(0); + + if(buff1) + free(buff1); + + return result; +} + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ + !(defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER < 0x3060000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(OPENSSL_IS_AWSLC) && \ + !defined(CURL_DISABLE_VERBOSE_STRINGS) +static void infof_certstack(struct Curl_easy *data, const SSL *ssl) +{ + STACK_OF(X509) *certstack; + long verify_result; + int num_cert_levels; + int cert_level; + + verify_result = SSL_get_verify_result(ssl); + if(verify_result != X509_V_OK) + certstack = SSL_get_peer_cert_chain(ssl); + else + certstack = SSL_get0_verified_chain(ssl); + num_cert_levels = sk_X509_num(certstack); + + for(cert_level = 0; cert_level < num_cert_levels; cert_level++) { + char cert_algorithm[80] = ""; + char group_name_final[80] = ""; + const X509_ALGOR *palg_cert = NULL; + const ASN1_OBJECT *paobj_cert = NULL; + X509 *current_cert; + EVP_PKEY *current_pkey; + int key_bits; + int key_sec_bits; + int get_group_name; + const char *type_name; + + current_cert = sk_X509_value(certstack, cert_level); + + X509_get0_signature(NULL, &palg_cert, current_cert); + X509_ALGOR_get0(&paobj_cert, NULL, NULL, palg_cert); + OBJ_obj2txt(cert_algorithm, sizeof(cert_algorithm), paobj_cert, 0); + + current_pkey = X509_get0_pubkey(current_cert); + key_bits = EVP_PKEY_bits(current_pkey); +#if (OPENSSL_VERSION_NUMBER < 0x30000000L) +#define EVP_PKEY_get_security_bits EVP_PKEY_security_bits +#endif + key_sec_bits = EVP_PKEY_get_security_bits(current_pkey); +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) + { + char group_name[80] = ""; + get_group_name = EVP_PKEY_get_group_name(current_pkey, group_name, + sizeof(group_name), NULL); + msnprintf(group_name_final, sizeof(group_name_final), "/%s", group_name); + } + type_name = EVP_PKEY_get0_type_name(current_pkey); +#else + get_group_name = 0; + type_name = NULL; +#endif + + infof(data, + " Certificate level %d: " + "Public key type %s%s (%d/%d Bits/secBits), signed using %s", + cert_level, type_name ? type_name : "?", + get_group_name == 0 ? "" : group_name_final, + key_bits, key_sec_bits, cert_algorithm); + } +} +#else +#define infof_certstack(data, ssl) +#endif + +/* + * Get the server cert, verify it and show it, etc., only call failf() if the + * 'strict' argument is TRUE as otherwise all this is for informational + * purposes only! + * + * We check certificates to authenticate the server; otherwise we risk + * man-in-the-middle attack. + */ +static CURLcode servercert(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool strict) +{ + struct connectdata *conn = cf->conn; + struct ssl_connect_data *connssl = cf->ctx; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + CURLcode result = CURLE_OK; + int rc; + long lerr; + X509 *issuer; + BIO *fp = NULL; + char error_buffer[256]=""; + char buffer[2048]; + const char *ptr; + BIO *mem = BIO_new(BIO_s_mem()); + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + + DEBUGASSERT(backend); + + if(!mem) { + failf(data, + "BIO_new return NULL, " OSSL_PACKAGE + " error %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer)) ); + return CURLE_OUT_OF_MEMORY; + } + + if(data->set.ssl.certinfo) + /* asked to gather certificate info */ + (void)Curl_ossl_certchain(data, backend->handle); + + backend->server_cert = SSL_get1_peer_certificate(backend->handle); + if(!backend->server_cert) { + BIO_free(mem); + if(!strict) + return CURLE_OK; + + failf(data, "SSL: couldn't get peer certificate"); + return CURLE_PEER_FAILED_VERIFICATION; + } + + infof(data, "%s certificate:", + Curl_ssl_cf_is_proxy(cf)? "Proxy" : "Server"); + + rc = x509_name_oneline(X509_get_subject_name(backend->server_cert), + buffer, sizeof(buffer)); + infof(data, " subject: %s", rc?"[NONE]":buffer); + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + { + long len; + ASN1_TIME_print(mem, X509_get0_notBefore(backend->server_cert)); + len = BIO_get_mem_data(mem, (char **) &ptr); + infof(data, " start date: %.*s", (int)len, ptr); + (void)BIO_reset(mem); + + ASN1_TIME_print(mem, X509_get0_notAfter(backend->server_cert)); + len = BIO_get_mem_data(mem, (char **) &ptr); + infof(data, " expire date: %.*s", (int)len, ptr); + (void)BIO_reset(mem); + } +#endif + + BIO_free(mem); + + if(conn_config->verifyhost) { + result = Curl_ossl_verifyhost(data, conn, &connssl->peer, + backend->server_cert); + if(result) { + X509_free(backend->server_cert); + backend->server_cert = NULL; + return result; + } + } + + rc = x509_name_oneline(X509_get_issuer_name(backend->server_cert), + buffer, sizeof(buffer)); + if(rc) { + if(strict) + failf(data, "SSL: couldn't get X509-issuer name"); + result = CURLE_PEER_FAILED_VERIFICATION; + } + else { + infof(data, " issuer: %s", buffer); + + /* We could do all sorts of certificate verification stuff here before + deallocating the certificate. */ + + /* e.g. match issuer name with provided issuer certificate */ + if(conn_config->issuercert || conn_config->issuercert_blob) { + if(conn_config->issuercert_blob) { + fp = BIO_new_mem_buf(conn_config->issuercert_blob->data, + (int)conn_config->issuercert_blob->len); + if(!fp) { + failf(data, + "BIO_new_mem_buf NULL, " OSSL_PACKAGE + " error %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer)) ); + X509_free(backend->server_cert); + backend->server_cert = NULL; + return CURLE_OUT_OF_MEMORY; + } + } + else { + fp = BIO_new(BIO_s_file()); + if(!fp) { + failf(data, + "BIO_new return NULL, " OSSL_PACKAGE + " error %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer)) ); + X509_free(backend->server_cert); + backend->server_cert = NULL; + return CURLE_OUT_OF_MEMORY; + } + + if(BIO_read_filename(fp, conn_config->issuercert) <= 0) { + if(strict) + failf(data, "SSL: Unable to open issuer cert (%s)", + conn_config->issuercert); + BIO_free(fp); + X509_free(backend->server_cert); + backend->server_cert = NULL; + return CURLE_SSL_ISSUER_ERROR; + } + } + + issuer = PEM_read_bio_X509(fp, NULL, ZERO_NULL, NULL); + if(!issuer) { + if(strict) + failf(data, "SSL: Unable to read issuer cert (%s)", + conn_config->issuercert); + BIO_free(fp); + X509_free(issuer); + X509_free(backend->server_cert); + backend->server_cert = NULL; + return CURLE_SSL_ISSUER_ERROR; + } + + if(X509_check_issued(issuer, backend->server_cert) != X509_V_OK) { + if(strict) + failf(data, "SSL: Certificate issuer check failed (%s)", + conn_config->issuercert); + BIO_free(fp); + X509_free(issuer); + X509_free(backend->server_cert); + backend->server_cert = NULL; + return CURLE_SSL_ISSUER_ERROR; + } + + infof(data, " SSL certificate issuer check ok (%s)", + conn_config->issuercert); + BIO_free(fp); + X509_free(issuer); + } + + lerr = SSL_get_verify_result(backend->handle); + ssl_config->certverifyresult = lerr; + if(lerr != X509_V_OK) { + if(conn_config->verifypeer) { + /* We probably never reach this, because SSL_connect() will fail + and we return earlier if verifypeer is set? */ + if(strict) + failf(data, "SSL certificate verify result: %s (%ld)", + X509_verify_cert_error_string(lerr), lerr); + result = CURLE_PEER_FAILED_VERIFICATION; + } + else + infof(data, " SSL certificate verify result: %s (%ld)," + " continuing anyway.", + X509_verify_cert_error_string(lerr), lerr); + } + else + infof(data, " SSL certificate verify ok."); + } + + infof_certstack(data, backend->handle); + +#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ + !defined(OPENSSL_NO_OCSP) + if(conn_config->verifystatus && !connssl->reused_session) { + /* don't do this after Session ID reuse */ + result = verifystatus(cf, data); + if(result) { + X509_free(backend->server_cert); + backend->server_cert = NULL; + return result; + } + } +#endif + + if(!strict) + /* when not strict, we don't bother about the verify cert problems */ + result = CURLE_OK; + + ptr = Curl_ssl_cf_is_proxy(cf)? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + if(!result && ptr) { + result = ossl_pkp_pin_peer_pubkey(data, backend->server_cert, ptr); + if(result) + failf(data, "SSL: public key does not match pinned public key"); + } + + X509_free(backend->server_cert); + backend->server_cert = NULL; + connssl->connecting_state = ssl_connect_done; + + return result; +} + +static CURLcode ossl_connect_step3(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct ssl_connect_data *connssl = cf->ctx; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + + DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + + /* + * We check certificates to authenticate the server; otherwise we risk + * man-in-the-middle attack; NEVERTHELESS, if we're told explicitly not to + * verify the peer, ignore faults and failures from the server cert + * operations. + */ + + result = servercert(cf, data, conn_config->verifypeer || + conn_config->verifyhost); + + if(!result) + connssl->connecting_state = ssl_connect_done; + + return result; +} + +static CURLcode ossl_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool nonblocking, + bool *done) +{ + CURLcode result = CURLE_OK; + struct ssl_connect_data *connssl = cf->ctx; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + int what; + + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + if(ssl_connect_1 == connssl->connecting_state) { + /* Find out how much more time we're allowed */ + const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time is already up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + result = ossl_connect_step1(cf, data); + if(result) + goto out; + } + + while(ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + + /* check allowed time left */ + const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + result = CURLE_OPERATION_TIMEDOUT; + goto out; + } + + /* if ssl is expecting something, check if it's available. */ + if(!nonblocking && + (connssl->connecting_state == ssl_connect_2_reading || + connssl->connecting_state == ssl_connect_2_writing)) { + + curl_socket_t writefd = ssl_connect_2_writing == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, + timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + if(0 == what) { + /* timeout */ + failf(data, "SSL connection timeout"); + result = CURLE_OPERATION_TIMEDOUT; + goto out; + } + /* socket is readable or writable */ + } + + /* Run transaction, and return to the caller if it failed or if this + * connection is done nonblocking and this loop would execute again. This + * permits the owner of a multi handle to abort a connection attempt + * before step2 has completed while ensuring that a client using select() + * or epoll() will always have a valid fdset to wait on. + */ + result = ossl_connect_step2(cf, data); + if(result || (nonblocking && + (ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state))) + goto out; + + } /* repeat step2 until all transactions are done. */ + + if(ssl_connect_3 == connssl->connecting_state) { + result = ossl_connect_step3(cf, data); + if(result) + goto out; + } + + if(ssl_connect_done == connssl->connecting_state) { + connssl->state = ssl_connection_complete; + *done = TRUE; + } + else + *done = FALSE; + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + +out: + return result; +} + +static CURLcode ossl_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + return ossl_connect_common(cf, data, TRUE, done); +} + +static CURLcode ossl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result; + bool done = FALSE; + + result = ossl_connect_common(cf, data, FALSE, &done); + if(result) + return result; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +static bool ossl_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + + (void)data; + DEBUGASSERT(connssl && backend); + if(backend->handle && SSL_pending(backend->handle)) + return TRUE; + return FALSE; +} + +static ssize_t ossl_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, + size_t len, + CURLcode *curlcode) +{ + /* SSL_write() is said to return 'int' while write() and send() returns + 'size_t' */ + int err; + char error_buffer[256]; + sslerr_t sslerror; + int memlen; + int rc; + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + + (void)data; + DEBUGASSERT(backend); + + ERR_clear_error(); + + memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; + rc = SSL_write(backend->handle, mem, memlen); + + if(rc <= 0) { + err = SSL_get_error(backend->handle, rc); + + switch(err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* The operation did not complete; the same TLS/SSL I/O function + should be called again later. This is basically an EWOULDBLOCK + equivalent. */ + *curlcode = CURLE_AGAIN; + rc = -1; + goto out; + case SSL_ERROR_SYSCALL: + { + int sockerr = SOCKERRNO; + + if(backend->io_result == CURLE_AGAIN) { + *curlcode = CURLE_AGAIN; + rc = -1; + goto out; + } + sslerror = ERR_get_error(); + if(sslerror) + ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); + else if(sockerr) + Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); + else { + strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer)); + error_buffer[sizeof(error_buffer) - 1] = '\0'; + } + failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d", + error_buffer, sockerr); + *curlcode = CURLE_SEND_ERROR; + rc = -1; + goto out; + } + case SSL_ERROR_SSL: { + /* A failure in the SSL library occurred, usually a protocol error. + The OpenSSL error queue contains more information on the error. */ + sslerror = ERR_get_error(); + failf(data, "SSL_write() error: %s", + ossl_strerror(sslerror, error_buffer, sizeof(error_buffer))); + *curlcode = CURLE_SEND_ERROR; + rc = -1; + goto out; + } + default: + /* a true error */ + failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d", + SSL_ERROR_to_str(err), SOCKERRNO); + *curlcode = CURLE_SEND_ERROR; + rc = -1; + goto out; + } + } + *curlcode = CURLE_OK; + +out: + return (ssize_t)rc; /* number of bytes */ +} + +static ssize_t ossl_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, /* transfer */ + char *buf, /* store read data here */ + size_t buffersize, /* max amount to read */ + CURLcode *curlcode) +{ + char error_buffer[256]; + unsigned long sslerror; + ssize_t nread; + int buffsize; + struct connectdata *conn = cf->conn; + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + + (void)data; + DEBUGASSERT(backend); + + ERR_clear_error(); + + buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; + nread = (ssize_t)SSL_read(backend->handle, buf, buffsize); + + if(nread <= 0) { + /* failed SSL_read */ + int err = SSL_get_error(backend->handle, (int)nread); + + switch(err) { + case SSL_ERROR_NONE: /* this is not an error */ + break; + case SSL_ERROR_ZERO_RETURN: /* no more data */ + /* close_notify alert */ + if(cf->sockindex == FIRSTSOCKET) + /* mark the connection for close if it is indeed the control + connection */ + connclose(conn, "TLS close_notify"); + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* there's data pending, re-invoke SSL_read() */ + *curlcode = CURLE_AGAIN; + nread = -1; + goto out; + default: + /* openssl/ssl.h for SSL_ERROR_SYSCALL says "look at error stack/return + value/errno" */ + /* https://www.openssl.org/docs/crypto/ERR_get_error.html */ + if(backend->io_result == CURLE_AGAIN) { + *curlcode = CURLE_AGAIN; + nread = -1; + goto out; + } + sslerror = ERR_get_error(); + if((nread < 0) || sslerror) { + /* If the return code was negative or there actually is an error in the + queue */ + int sockerr = SOCKERRNO; + if(sslerror) + ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); + else if(sockerr && err == SSL_ERROR_SYSCALL) + Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); + else { + strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer)); + error_buffer[sizeof(error_buffer) - 1] = '\0'; + } + failf(data, OSSL_PACKAGE " SSL_read: %s, errno %d", + error_buffer, sockerr); + *curlcode = CURLE_RECV_ERROR; + nread = -1; + goto out; + } + /* For debug builds be a little stricter and error on any + SSL_ERROR_SYSCALL. For example a server may have closed the connection + abruptly without a close_notify alert. For compatibility with older + peers we don't do this by default. #4624 + + We can use this to gauge how many users may be affected, and + if it goes ok eventually transition to allow in dev and release with + the newest OpenSSL: #if (OPENSSL_VERSION_NUMBER >= 0x10101000L) */ +#ifdef DEBUGBUILD + if(err == SSL_ERROR_SYSCALL) { + int sockerr = SOCKERRNO; + if(sockerr) + Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); + else { + msnprintf(error_buffer, sizeof(error_buffer), + "Connection closed abruptly"); + } + failf(data, OSSL_PACKAGE " SSL_read: %s, errno %d" + " (Fatal because this is a curl debug build)", + error_buffer, sockerr); + *curlcode = CURLE_RECV_ERROR; + nread = -1; + goto out; + } +#endif + } + } + +out: + return nread; +} + +static size_t ossl_version(char *buffer, size_t size) +{ +#ifdef LIBRESSL_VERSION_NUMBER +#ifdef HAVE_OPENSSL_VERSION + char *p; + int count; + const char *ver = OpenSSL_version(OPENSSL_VERSION); + const char expected[] = OSSL_PACKAGE " "; /* ie "LibreSSL " */ + if(strncasecompare(ver, expected, sizeof(expected) - 1)) { + ver += sizeof(expected) - 1; + } + count = msnprintf(buffer, size, "%s/%s", OSSL_PACKAGE, ver); + for(p = buffer; *p; ++p) { + if(ISBLANK(*p)) + *p = '_'; + } + return count; +#else + return msnprintf(buffer, size, "%s/%lx.%lx.%lx", + OSSL_PACKAGE, + (LIBRESSL_VERSION_NUMBER>>28)&0xf, + (LIBRESSL_VERSION_NUMBER>>20)&0xff, + (LIBRESSL_VERSION_NUMBER>>12)&0xff); +#endif +#elif defined(OPENSSL_IS_BORINGSSL) +#ifdef CURL_BORINGSSL_VERSION + return msnprintf(buffer, size, "%s/%s", + OSSL_PACKAGE, + CURL_BORINGSSL_VERSION); +#else + return msnprintf(buffer, size, OSSL_PACKAGE); +#endif +#elif defined(OPENSSL_IS_AWSLC) + return msnprintf(buffer, size, "%s/%s", + OSSL_PACKAGE, + AWSLC_VERSION_NUMBER_STRING); +#elif defined(HAVE_OPENSSL_VERSION) && defined(OPENSSL_VERSION_STRING) + return msnprintf(buffer, size, "%s/%s", + OSSL_PACKAGE, OpenSSL_version(OPENSSL_VERSION_STRING)); +#else + /* not LibreSSL, BoringSSL and not using OpenSSL_version */ + + char sub[3]; + unsigned long ssleay_value; + sub[2]='\0'; + sub[1]='\0'; + ssleay_value = OpenSSL_version_num(); + if(ssleay_value < 0x906000) { + ssleay_value = SSLEAY_VERSION_NUMBER; + sub[0]='\0'; + } + else { + if(ssleay_value&0xff0) { + int minor_ver = (ssleay_value >> 4) & 0xff; + if(minor_ver > 26) { + /* handle extended version introduced for 0.9.8za */ + sub[1] = (char) ((minor_ver - 1) % 26 + 'a' + 1); + sub[0] = 'z'; + } + else { + sub[0] = (char) (minor_ver + 'a' - 1); + } + } + else + sub[0]='\0'; + } + + return msnprintf(buffer, size, "%s/%lx.%lx.%lx%s" +#ifdef OPENSSL_FIPS + "-fips" +#endif + , + OSSL_PACKAGE, + (ssleay_value>>28)&0xf, + (ssleay_value>>20)&0xff, + (ssleay_value>>12)&0xff, + sub); +#endif /* OPENSSL_IS_BORINGSSL */ +} + +/* can be called with data == NULL */ +static CURLcode ossl_random(struct Curl_easy *data, + unsigned char *entropy, size_t length) +{ + int rc; + if(data) { + if(ossl_seed(data)) /* Initiate the seed if not already done */ + return CURLE_FAILED_INIT; /* couldn't seed for some reason */ + } + else { + if(!rand_enough()) + return CURLE_FAILED_INIT; + } + /* RAND_bytes() returns 1 on success, 0 otherwise. */ + rc = RAND_bytes(entropy, curlx_uztosi(length)); + return (rc == 1 ? CURLE_OK : CURLE_FAILED_INIT); +} + +#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) +static CURLcode ossl_sha256sum(const unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *sha256sum /* output */, + size_t unused) +{ + EVP_MD_CTX *mdctx; + unsigned int len = 0; + (void) unused; + + mdctx = EVP_MD_CTX_create(); + if(!mdctx) + return CURLE_OUT_OF_MEMORY; + if(!EVP_DigestInit(mdctx, EVP_sha256())) { + EVP_MD_CTX_destroy(mdctx); + return CURLE_FAILED_INIT; + } + EVP_DigestUpdate(mdctx, tmp, tmplen); + EVP_DigestFinal_ex(mdctx, sha256sum, &len); + EVP_MD_CTX_destroy(mdctx); + return CURLE_OK; +} +#endif + +static bool ossl_cert_status_request(void) +{ +#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ + !defined(OPENSSL_NO_OCSP) + return TRUE; +#else + return FALSE; +#endif +} + +static void *ossl_get_internals(struct ssl_connect_data *connssl, + CURLINFO info) +{ + /* Legacy: CURLINFO_TLS_SESSION must return an SSL_CTX pointer. */ + struct ossl_ssl_backend_data *backend = + (struct ossl_ssl_backend_data *)connssl->backend; + DEBUGASSERT(backend); + return info == CURLINFO_TLS_SESSION ? + (void *)backend->ctx : (void *)backend->handle; +} + +static void ossl_free_multi_ssl_backend_data( + struct multi_ssl_backend_data *mbackend) +{ +#if defined(HAVE_SSL_X509_STORE_SHARE) + if(mbackend->store) { + X509_STORE_free(mbackend->store); + } + free(mbackend->CAfile); + free(mbackend); +#else /* HAVE_SSL_X509_STORE_SHARE */ + (void)mbackend; +#endif /* HAVE_SSL_X509_STORE_SHARE */ +} + +const struct Curl_ssl Curl_ssl_openssl = { + { CURLSSLBACKEND_OPENSSL, "openssl" }, /* info */ + + SSLSUPP_CA_PATH | + SSLSUPP_CAINFO_BLOB | + SSLSUPP_CERTINFO | + SSLSUPP_PINNEDPUBKEY | + SSLSUPP_SSL_CTX | +#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES + SSLSUPP_TLS13_CIPHERSUITES | +#endif + SSLSUPP_HTTPS_PROXY, + + sizeof(struct ossl_ssl_backend_data), + + ossl_init, /* init */ + ossl_cleanup, /* cleanup */ + ossl_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + ossl_shutdown, /* shutdown */ + ossl_data_pending, /* data_pending */ + ossl_random, /* random */ + ossl_cert_status_request, /* cert_status_request */ + ossl_connect, /* connect */ + ossl_connect_nonblocking, /* connect_nonblocking */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ + ossl_get_internals, /* get_internals */ + ossl_close, /* close_one */ + ossl_close_all, /* close_all */ + ossl_session_free, /* session_free */ + ossl_set_engine, /* set_engine */ + ossl_set_engine_default, /* set_engine_default */ + ossl_engines_list, /* engines_list */ + Curl_none_false_start, /* false_start */ +#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) + ossl_sha256sum, /* sha256sum */ +#else + NULL, /* sha256sum */ +#endif + NULL, /* use of data in this connection */ + NULL, /* remote of data from this connection */ + ossl_free_multi_ssl_backend_data, /* free_multi_ssl_backend_data */ + ossl_recv, /* recv decrypted data */ + ossl_send, /* send data to encrypt */ +}; + +#endif /* USE_OPENSSL */ diff --git a/Utilities/cmcurl/lib/vtls/openssl.h b/Utilities/cmcurl/lib/vtls/openssl.h new file mode 100644 index 0000000..e802363 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/openssl.h @@ -0,0 +1,70 @@ +#ifndef HEADER_CURL_SSLUSE_H +#define HEADER_CURL_SSLUSE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_OPENSSL +/* + * This header should only be needed to get included by vtls.c, openssl.c + * and ngtcp2.c + */ +#include <openssl/ossl_typ.h> +#include <openssl/ssl.h> + +#include "urldata.h" + +#if (OPENSSL_VERSION_NUMBER < 0x30000000L) +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#endif + +CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, + struct ssl_peer *peer, X509 *server_cert); +extern const struct Curl_ssl Curl_ssl_openssl; + +CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data, + SSL_CTX *ctx, char *cert_file, + const struct curl_blob *cert_blob, + const char *cert_type, char *key_file, + const struct curl_blob *key_blob, + const char *key_type, char *key_passwd); + +CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl); + +/** + * Setup the OpenSSL X509_STORE in `ssl_ctx` for the cfilter `cf` and + * easy handle `data`. Will allow reuse of a shared cache if suitable + * and configured. + */ +CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + SSL_CTX *ssl_ctx); + +CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf, + struct Curl_easy *data, + SSL_CTX *ssl_ctx); + +#endif /* USE_OPENSSL */ +#endif /* HEADER_CURL_SSLUSE_H */ diff --git a/Utilities/cmcurl/lib/vtls/rustls.c b/Utilities/cmcurl/lib/vtls/rustls.c new file mode 100644 index 0000000..8751fd9 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/rustls.c @@ -0,0 +1,730 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jacob Hoffman-Andrews, + * <github@hoffman-andrews.com> + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_RUSTLS + +#include "curl_printf.h" + +#include <errno.h> +#include <rustls.h> + +#include "inet_pton.h" +#include "urldata.h" +#include "sendf.h" +#include "vtls.h" +#include "vtls_int.h" +#include "select.h" +#include "strerror.h" +#include "multiif.h" +#include "connect.h" /* for the connect timeout */ + +struct rustls_ssl_backend_data +{ + const struct rustls_client_config *config; + struct rustls_connection *conn; + bool data_pending; +}; + +/* For a given rustls_result error code, return the best-matching CURLcode. */ +static CURLcode map_error(rustls_result r) +{ + if(rustls_result_is_cert_error(r)) { + return CURLE_PEER_FAILED_VERIFICATION; + } + switch(r) { + case RUSTLS_RESULT_OK: + return CURLE_OK; + case RUSTLS_RESULT_NULL_PARAMETER: + return CURLE_BAD_FUNCTION_ARGUMENT; + default: + return CURLE_READ_ERROR; + } +} + +static bool +cr_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) +{ + struct ssl_connect_data *ctx = cf->ctx; + struct rustls_ssl_backend_data *backend; + + (void)data; + DEBUGASSERT(ctx && ctx->backend); + backend = (struct rustls_ssl_backend_data *)ctx->backend; + return backend->data_pending; +} + +struct io_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; +}; + +static int +read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n) +{ + struct io_ctx *io_ctx = userdata; + CURLcode result; + int ret = 0; + ssize_t nread = Curl_conn_cf_recv(io_ctx->cf->next, io_ctx->data, + (char *)buf, len, &result); + if(nread < 0) { + nread = 0; + if(CURLE_AGAIN == result) + ret = EAGAIN; + else + ret = EINVAL; + } + *out_n = (int)nread; + return ret; +} + +static int +write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n) +{ + struct io_ctx *io_ctx = userdata; + CURLcode result; + int ret = 0; + ssize_t nwritten = Curl_conn_cf_send(io_ctx->cf->next, io_ctx->data, + (const char *)buf, len, &result); + if(nwritten < 0) { + nwritten = 0; + if(CURLE_AGAIN == result) + ret = EAGAIN; + else + ret = EINVAL; + } + *out_n = (int)nwritten; + /* + CURL_TRC_CFX(io_ctx->data, io_ctx->cf, "cf->next send(len=%zu) -> %zd, %d", + len, nwritten, result)); + */ + return ret; +} + +static ssize_t tls_recv_more(struct Curl_cfilter *cf, + struct Curl_easy *data, CURLcode *err) +{ + struct ssl_connect_data *const connssl = cf->ctx; + struct rustls_ssl_backend_data *const backend = + (struct rustls_ssl_backend_data *)connssl->backend; + struct io_ctx io_ctx; + size_t tls_bytes_read = 0; + rustls_io_result io_error; + rustls_result rresult = 0; + + io_ctx.cf = cf; + io_ctx.data = data; + io_error = rustls_connection_read_tls(backend->conn, read_cb, &io_ctx, + &tls_bytes_read); + if(io_error == EAGAIN || io_error == EWOULDBLOCK) { + *err = CURLE_AGAIN; + return -1; + } + else if(io_error) { + char buffer[STRERROR_LEN]; + failf(data, "reading from socket: %s", + Curl_strerror(io_error, buffer, sizeof(buffer))); + *err = CURLE_READ_ERROR; + return -1; + } + + rresult = rustls_connection_process_new_packets(backend->conn); + if(rresult != RUSTLS_RESULT_OK) { + char errorbuf[255]; + size_t errorlen; + rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "rustls_connection_process_new_packets: %.*s", + errorlen, errorbuf); + *err = map_error(rresult); + return -1; + } + + backend->data_pending = TRUE; + *err = CURLE_OK; + return (ssize_t)tls_bytes_read; +} + +/* + * On each run: + * - Read a chunk of bytes from the socket into rustls' TLS input buffer. + * - Tell rustls to process any new packets. + * - Read out as many plaintext bytes from rustls as possible, until hitting + * error, EOF, or EAGAIN/EWOULDBLOCK, or plainbuf/plainlen is filled up. + * + * It's okay to call this function with plainbuf == NULL and plainlen == 0. + * In that case, it will copy bytes from the socket into rustls' TLS input + * buffer, and process packets, but won't consume bytes from rustls' plaintext + * output buffer. + */ +static ssize_t +cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *plainbuf, size_t plainlen, CURLcode *err) +{ + struct ssl_connect_data *const connssl = cf->ctx; + struct rustls_ssl_backend_data *const backend = + (struct rustls_ssl_backend_data *)connssl->backend; + struct rustls_connection *rconn = NULL; + size_t n = 0; + size_t plain_bytes_copied = 0; + rustls_result rresult = 0; + ssize_t nread; + bool eof = FALSE; + + DEBUGASSERT(backend); + rconn = backend->conn; + + while(plain_bytes_copied < plainlen) { + if(!backend->data_pending) { + if(tls_recv_more(cf, data, err) < 0) { + if(*err != CURLE_AGAIN) { + nread = -1; + goto out; + } + break; + } + } + + rresult = rustls_connection_read(rconn, + (uint8_t *)plainbuf + plain_bytes_copied, + plainlen - plain_bytes_copied, + &n); + if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) { + backend->data_pending = FALSE; + } + else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) { + failf(data, "rustls: peer closed TCP connection " + "without first closing TLS connection"); + *err = CURLE_READ_ERROR; + nread = -1; + goto out; + } + else if(rresult != RUSTLS_RESULT_OK) { + /* n always equals 0 in this case, don't need to check it */ + char errorbuf[255]; + size_t errorlen; + rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "rustls_connection_read: %.*s", errorlen, errorbuf); + *err = CURLE_READ_ERROR; + nread = -1; + goto out; + } + else if(n == 0) { + /* n == 0 indicates clean EOF, but we may have read some other + plaintext bytes before we reached this. Break out of the loop + so we can figure out whether to return success or EOF. */ + eof = TRUE; + break; + } + else { + plain_bytes_copied += n; + } + } + + if(plain_bytes_copied) { + *err = CURLE_OK; + nread = (ssize_t)plain_bytes_copied; + } + else if(eof) { + *err = CURLE_OK; + nread = 0; + } + else { + *err = CURLE_AGAIN; + nread = -1; + } + +out: + CURL_TRC_CF(data, cf, "cf_recv(len=%zu) -> %zd, %d", + plainlen, nread, *err); + return nread; +} + +/* + * On each call: + * - Copy `plainlen` bytes into rustls' plaintext input buffer (if > 0). + * - Fully drain rustls' plaintext output buffer into the socket until + * we get either an error or EAGAIN/EWOULDBLOCK. + * + * It's okay to call this function with plainbuf == NULL and plainlen == 0. + * In that case, it won't read anything into rustls' plaintext input buffer. + * It will only drain rustls' plaintext output buffer into the socket. + */ +static ssize_t +cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *plainbuf, size_t plainlen, CURLcode *err) +{ + struct ssl_connect_data *const connssl = cf->ctx; + struct rustls_ssl_backend_data *const backend = + (struct rustls_ssl_backend_data *)connssl->backend; + struct rustls_connection *rconn = NULL; + struct io_ctx io_ctx; + size_t plainwritten = 0; + size_t tlswritten = 0; + size_t tlswritten_total = 0; + rustls_result rresult; + rustls_io_result io_error; + char errorbuf[256]; + size_t errorlen; + + DEBUGASSERT(backend); + rconn = backend->conn; + + CURL_TRC_CF(data, cf, "cf_send: %ld plain bytes", plainlen); + + io_ctx.cf = cf; + io_ctx.data = data; + + if(plainlen > 0) { + rresult = rustls_connection_write(rconn, plainbuf, plainlen, + &plainwritten); + if(rresult != RUSTLS_RESULT_OK) { + rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "rustls_connection_write: %.*s", errorlen, errorbuf); + *err = CURLE_WRITE_ERROR; + return -1; + } + else if(plainwritten == 0) { + failf(data, "rustls_connection_write: EOF"); + *err = CURLE_WRITE_ERROR; + return -1; + } + } + + while(rustls_connection_wants_write(rconn)) { + io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx, + &tlswritten); + if(io_error == EAGAIN || io_error == EWOULDBLOCK) { + CURL_TRC_CF(data, cf, "cf_send: EAGAIN after %zu bytes", + tlswritten_total); + *err = CURLE_AGAIN; + return -1; + } + else if(io_error) { + char buffer[STRERROR_LEN]; + failf(data, "writing to socket: %s", + Curl_strerror(io_error, buffer, sizeof(buffer))); + *err = CURLE_WRITE_ERROR; + return -1; + } + if(tlswritten == 0) { + failf(data, "EOF in swrite"); + *err = CURLE_WRITE_ERROR; + return -1; + } + CURL_TRC_CF(data, cf, "cf_send: wrote %zu TLS bytes", tlswritten); + tlswritten_total += tlswritten; + } + + return plainwritten; +} + +/* A server certificate verify callback for rustls that always returns + RUSTLS_RESULT_OK, or in other words disable certificate verification. */ +static enum rustls_result +cr_verify_none(void *userdata UNUSED_PARAM, + const rustls_verify_server_cert_params *params UNUSED_PARAM) +{ + return RUSTLS_RESULT_OK; +} + +static bool +cr_hostname_is_ip(const char *hostname) +{ + struct in_addr in; +#ifdef ENABLE_IPV6 + struct in6_addr in6; + if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) { + return true; + } +#endif /* ENABLE_IPV6 */ + if(Curl_inet_pton(AF_INET, hostname, &in) > 0) { + return true; + } + return false; +} + +static CURLcode +cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, + struct rustls_ssl_backend_data *const backend) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct rustls_connection *rconn = NULL; + struct rustls_client_config_builder *config_builder = NULL; + struct rustls_root_cert_store *roots = NULL; + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + const char * const ssl_cafile = + /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ + (ca_info_blob ? NULL : conn_config->CAfile); + const bool verifypeer = conn_config->verifypeer; + const char *hostname = connssl->peer.hostname; + char errorbuf[256]; + size_t errorlen; + int result; + + DEBUGASSERT(backend); + rconn = backend->conn; + + config_builder = rustls_client_config_builder_new(); + if(connssl->alpn) { + struct alpn_proto_buf proto; + rustls_slice_bytes alpn[ALPN_ENTRIES_MAX]; + size_t i; + + for(i = 0; i < connssl->alpn->count; ++i) { + alpn[i].data = (const uint8_t *)connssl->alpn->entries[i]; + alpn[i].len = strlen(connssl->alpn->entries[i]); + } + rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, + connssl->alpn->count); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } + if(!verifypeer) { + rustls_client_config_builder_dangerous_set_certificate_verifier( + config_builder, cr_verify_none); + /* rustls doesn't support IP addresses (as of 0.19.0), and will reject + * connections created with an IP address, even when certificate + * verification is turned off. Set a placeholder hostname and disable + * SNI. */ + if(cr_hostname_is_ip(hostname)) { + rustls_client_config_builder_set_enable_sni(config_builder, false); + hostname = "example.invalid"; + } + } + else if(ca_info_blob) { + roots = rustls_root_cert_store_new(); + + /* Enable strict parsing only if verification isn't disabled. */ + result = rustls_root_cert_store_add_pem(roots, ca_info_blob->data, + ca_info_blob->len, verifypeer); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to parse trusted certificates from blob"); + rustls_root_cert_store_free(roots); + rustls_client_config_free( + rustls_client_config_builder_build(config_builder)); + return CURLE_SSL_CACERT_BADFILE; + } + + result = rustls_client_config_builder_use_roots(config_builder, roots); + rustls_root_cert_store_free(roots); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to load trusted certificates"); + rustls_client_config_free( + rustls_client_config_builder_build(config_builder)); + return CURLE_SSL_CACERT_BADFILE; + } + } + else if(ssl_cafile) { + result = rustls_client_config_builder_load_roots_from_file( + config_builder, ssl_cafile); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to load trusted certificates"); + rustls_client_config_free( + rustls_client_config_builder_build(config_builder)); + return CURLE_SSL_CACERT_BADFILE; + } + } + + backend->config = rustls_client_config_builder_build(config_builder); + DEBUGASSERT(rconn == NULL); + { + /* rustls claims to manage ip address hostnames as well here. So, + * if we have an SNI, we use it, otherwise we pass the hostname */ + char *server = connssl->peer.sni? + connssl->peer.sni : connssl->peer.hostname; + result = rustls_client_connection_new(backend->config, server, &rconn); + } + if(result != RUSTLS_RESULT_OK) { + rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "rustls_client_connection_new: %.*s", errorlen, errorbuf); + return CURLE_COULDNT_CONNECT; + } + rustls_connection_set_userdata(rconn, backend); + backend->conn = rconn; + return CURLE_OK; +} + +static void +cr_set_negotiated_alpn(struct Curl_cfilter *cf, struct Curl_easy *data, + const struct rustls_connection *rconn) +{ + const uint8_t *protocol = NULL; + size_t len = 0; + + rustls_connection_get_alpn_protocol(rconn, &protocol, &len); + Curl_alpn_set_negotiated(cf, data, protocol, len); +} + +/* Given an established network connection, do a TLS handshake. + * + * If `blocking` is true, this function will block until the handshake is + * complete. Otherwise it will return as soon as I/O would block. + * + * For the non-blocking I/O case, this function will set `*done` to true + * once the handshake is complete. This function never reads the value of + * `*done*`. + */ +static CURLcode +cr_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, + bool *done) +{ + struct ssl_connect_data *const connssl = cf->ctx; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + struct rustls_ssl_backend_data *const backend = + (struct rustls_ssl_backend_data *)connssl->backend; + struct rustls_connection *rconn = NULL; + CURLcode tmperr = CURLE_OK; + int result; + int what; + bool wants_read; + bool wants_write; + curl_socket_t writefd; + curl_socket_t readfd; + timediff_t timeout_ms; + timediff_t socket_check_timeout; + + DEBUGASSERT(backend); + + if(ssl_connection_none == connssl->state) { + result = cr_init_backend(cf, data, + (struct rustls_ssl_backend_data *)connssl->backend); + if(result != CURLE_OK) { + return result; + } + connssl->state = ssl_connection_negotiating; + } + + rconn = backend->conn; + + /* Read/write data until the handshake is done or the socket would block. */ + for(;;) { + /* + * Connection has been established according to rustls. Set send/recv + * handlers, and update the state machine. + */ + if(!rustls_connection_is_handshaking(rconn)) { + infof(data, "Done handshaking"); + /* Done with the handshake. Set up callbacks to send/receive data. */ + connssl->state = ssl_connection_complete; + + cr_set_negotiated_alpn(cf, data, rconn); + + *done = TRUE; + return CURLE_OK; + } + + wants_read = rustls_connection_wants_read(rconn); + wants_write = rustls_connection_wants_write(rconn); + DEBUGASSERT(wants_read || wants_write); + writefd = wants_write?sockfd:CURL_SOCKET_BAD; + readfd = wants_read?sockfd:CURL_SOCKET_BAD; + + /* check allowed time left */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "rustls: operation timed out before socket check"); + return CURLE_OPERATION_TIMEDOUT; + } + + socket_check_timeout = blocking?timeout_ms:0; + + what = Curl_socket_check( + readfd, CURL_SOCKET_BAD, writefd, socket_check_timeout); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + if(blocking && 0 == what) { + failf(data, "rustls connection timeout after %d ms", + socket_check_timeout); + return CURLE_OPERATION_TIMEDOUT; + } + if(0 == what) { + infof(data, "Curl_socket_check: %s would block", + wants_read&&wants_write ? "writing and reading" : + wants_write ? "writing" : "reading"); + *done = FALSE; + return CURLE_OK; + } + /* socket is readable or writable */ + + if(wants_write) { + infof(data, "rustls_connection wants us to write_tls."); + cr_send(cf, data, NULL, 0, &tmperr); + if(tmperr == CURLE_AGAIN) { + infof(data, "writing would block"); + /* fall through */ + } + else if(tmperr != CURLE_OK) { + return tmperr; + } + } + + if(wants_read) { + infof(data, "rustls_connection wants us to read_tls."); + + if(tls_recv_more(cf, data, &tmperr) < 0) { + if(tmperr == CURLE_AGAIN) { + infof(data, "reading would block"); + /* fall through */ + } + else if(tmperr == CURLE_READ_ERROR) { + return CURLE_SSL_CONNECT_ERROR; + } + else { + return tmperr; + } + } + } + } + + /* We should never fall through the loop. We should return either because + the handshake is done or because we can't read/write without blocking. */ + DEBUGASSERT(false); +} + +static CURLcode +cr_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + return cr_connect_common(cf, data, false, done); +} + +static CURLcode +cr_connect_blocking(struct Curl_cfilter *cf UNUSED_PARAM, + struct Curl_easy *data UNUSED_PARAM) +{ + bool done; /* unused */ + return cr_connect_common(cf, data, true, &done); +} + +static void cr_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + if(!cf->connected) { + curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); + struct ssl_connect_data *const connssl = cf->ctx; + struct rustls_ssl_backend_data *const backend = + (struct rustls_ssl_backend_data *)connssl->backend; + struct rustls_connection *rconn = NULL; + + (void)data; + DEBUGASSERT(backend); + rconn = backend->conn; + + if(rustls_connection_wants_write(rconn)) { + Curl_pollset_add_out(data, ps, sock); + } + if(rustls_connection_wants_read(rconn)) { + Curl_pollset_add_in(data, ps, sock); + } + } +} + +static void * +cr_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) +{ + struct rustls_ssl_backend_data *backend = + (struct rustls_ssl_backend_data *)connssl->backend; + DEBUGASSERT(backend); + return &backend->conn; +} + +static void +cr_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct rustls_ssl_backend_data *backend = + (struct rustls_ssl_backend_data *)connssl->backend; + CURLcode tmperr = CURLE_OK; + ssize_t n = 0; + + DEBUGASSERT(backend); + + if(backend->conn) { + rustls_connection_send_close_notify(backend->conn); + n = cr_send(cf, data, NULL, 0, &tmperr); + if(n < 0) { + failf(data, "rustls: error sending close_notify: %d", tmperr); + } + + rustls_connection_free(backend->conn); + backend->conn = NULL; + } + if(backend->config) { + rustls_client_config_free(backend->config); + backend->config = NULL; + } +} + +static size_t cr_version(char *buffer, size_t size) +{ + struct rustls_str ver = rustls_version(); + return msnprintf(buffer, size, "%.*s", (int)ver.len, ver.data); +} + +const struct Curl_ssl Curl_ssl_rustls = { + { CURLSSLBACKEND_RUSTLS, "rustls" }, + SSLSUPP_CAINFO_BLOB | /* supports */ + SSLSUPP_TLS13_CIPHERSUITES | + SSLSUPP_HTTPS_PROXY, + sizeof(struct rustls_ssl_backend_data), + + Curl_none_init, /* init */ + Curl_none_cleanup, /* cleanup */ + cr_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + Curl_none_shutdown, /* shutdown */ + cr_data_pending, /* data_pending */ + Curl_none_random, /* random */ + Curl_none_cert_status_request, /* cert_status_request */ + cr_connect_blocking, /* connect */ + cr_connect_nonblocking, /* connect_nonblocking */ + cr_adjust_pollset, /* adjust_pollset */ + cr_get_internals, /* get_internals */ + cr_close, /* close_one */ + Curl_none_close_all, /* close_all */ + Curl_none_session_free, /* session_free */ + Curl_none_set_engine, /* set_engine */ + Curl_none_set_engine_default, /* set_engine_default */ + Curl_none_engines_list, /* engines_list */ + Curl_none_false_start, /* false_start */ + NULL, /* sha256sum */ + NULL, /* associate_connection */ + NULL, /* disassociate_connection */ + NULL, /* free_multi_ssl_backend_data */ + cr_recv, /* recv decrypted data */ + cr_send, /* send data to encrypt */ +}; + +#endif /* USE_RUSTLS */ diff --git a/Utilities/cmcurl/lib/vtls/rustls.h b/Utilities/cmcurl/lib/vtls/rustls.h new file mode 100644 index 0000000..bfbe23d --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/rustls.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jacob Hoffman-Andrews, + * <github@hoffman-andrews.com> + * + * 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 + * + ***************************************************************************/ +#ifndef HEADER_CURL_RUSTLS_H +#define HEADER_CURL_RUSTLS_H + +#include "curl_setup.h" + +#ifdef USE_RUSTLS + +extern const struct Curl_ssl Curl_ssl_rustls; + +#endif /* USE_RUSTLS */ +#endif /* HEADER_CURL_RUSTLS_H */ diff --git a/Utilities/cmcurl/lib/vtls/schannel.c b/Utilities/cmcurl/lib/vtls/schannel.c new file mode 100644 index 0000000..ae7f295 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/schannel.c @@ -0,0 +1,2929 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Marc Hoersken, <info@marc-hoersken.de> + * Copyright (C) Mark Salisbury, <mark.salisbury@hp.com> + * + * 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 + * + ***************************************************************************/ + +/* + * Source file for all Schannel-specific code for the TLS/SSL layer. No code + * but vtls.c should ever call or use these functions. + */ + +#include "curl_setup.h" + +#ifdef USE_SCHANNEL + +#ifndef USE_WINDOWS_SSPI +# error "Can't compile SCHANNEL support without SSPI." +#endif + +#include "schannel.h" +#include "schannel_int.h" +#include "vtls.h" +#include "vtls_int.h" +#include "strcase.h" +#include "sendf.h" +#include "connect.h" /* for the connect timeout */ +#include "strerror.h" +#include "select.h" /* for the socket readiness */ +#include "inet_pton.h" /* for IP addr SNI check */ +#include "curl_multibyte.h" +#include "warnless.h" +#include "x509asn1.h" +#include "curl_printf.h" +#include "multiif.h" +#include "version_win32.h" +#include "rand.h" + +/* The last #include file should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* ALPN requires version 8.1 of the Windows SDK, which was + shipped with Visual Studio 2013, aka _MSC_VER 1800: + + https://technet.microsoft.com/en-us/library/hh831771%28v=ws.11%29.aspx +*/ +#if defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_USING_V110_SDK71_) +# define HAS_ALPN 1 +#endif + +#ifndef BCRYPT_CHACHA20_POLY1305_ALGORITHM +#define BCRYPT_CHACHA20_POLY1305_ALGORITHM L"CHACHA20_POLY1305" +#endif + +#ifndef BCRYPT_CHAIN_MODE_CCM +#define BCRYPT_CHAIN_MODE_CCM L"ChainingModeCCM" +#endif + +#ifndef BCRYPT_CHAIN_MODE_GCM +#define BCRYPT_CHAIN_MODE_GCM L"ChainingModeGCM" +#endif + +#ifndef BCRYPT_AES_ALGORITHM +#define BCRYPT_AES_ALGORITHM L"AES" +#endif + +#ifndef BCRYPT_SHA256_ALGORITHM +#define BCRYPT_SHA256_ALGORITHM L"SHA256" +#endif + +#ifndef BCRYPT_SHA384_ALGORITHM +#define BCRYPT_SHA384_ALGORITHM L"SHA384" +#endif + +#ifdef HAS_CLIENT_CERT_PATH +#ifdef UNICODE +#define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_W +#else +#define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_A +#endif +#endif + +#ifndef SP_PROT_TLS1_0_CLIENT +#define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT +#endif + +#ifndef SP_PROT_TLS1_1_CLIENT +#define SP_PROT_TLS1_1_CLIENT 0x00000200 +#endif + +#ifndef SP_PROT_TLS1_2_CLIENT +#define SP_PROT_TLS1_2_CLIENT 0x00000800 +#endif + +#ifndef SP_PROT_TLS1_3_CLIENT +#define SP_PROT_TLS1_3_CLIENT 0x00002000 +#endif + +#ifndef SCH_USE_STRONG_CRYPTO +#define SCH_USE_STRONG_CRYPTO 0x00400000 +#endif + +#ifndef SECBUFFER_ALERT +#define SECBUFFER_ALERT 17 +#endif + +/* Both schannel buffer sizes must be > 0 */ +#define CURL_SCHANNEL_BUFFER_INIT_SIZE 4096 +#define CURL_SCHANNEL_BUFFER_FREE_SIZE 1024 + +#define CERT_THUMBPRINT_STR_LEN 40 +#define CERT_THUMBPRINT_DATA_LEN 20 + +/* Uncomment to force verbose output + * #define infof(x, y, ...) printf(y, __VA_ARGS__) + * #define failf(x, y, ...) printf(y, __VA_ARGS__) + */ + +#ifndef CALG_SHA_256 +# define CALG_SHA_256 0x0000800c +#endif + +#ifndef PKCS12_NO_PERSIST_KEY +#define PKCS12_NO_PERSIST_KEY 0x00008000 +#endif + +static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *pinnedpubkey); + +static void InitSecBuffer(SecBuffer *buffer, unsigned long BufType, + void *BufDataPtr, unsigned long BufByteSize) +{ + buffer->cbBuffer = BufByteSize; + buffer->BufferType = BufType; + buffer->pvBuffer = BufDataPtr; +} + +static void InitSecBufferDesc(SecBufferDesc *desc, SecBuffer *BufArr, + unsigned long NumArrElem) +{ + desc->ulVersion = SECBUFFER_VERSION; + desc->pBuffers = BufArr; + desc->cBuffers = NumArrElem; +} + +static CURLcode +schannel_set_ssl_version_min_max(DWORD *enabled_protocols, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + long ssl_version = conn_config->version; + long ssl_version_max = conn_config->version_max; + long i = ssl_version; + + switch(ssl_version_max) { + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_DEFAULT: + + /* 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. + */ + if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_3; + } + else /* Windows 10 and older */ + ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; + + break; + } + + for(; i <= (ssl_version_max >> 16); ++i) { + switch(i) { + case CURL_SSLVERSION_TLSv1_0: + (*enabled_protocols) |= SP_PROT_TLS1_0_CLIENT; + break; + case CURL_SSLVERSION_TLSv1_1: + (*enabled_protocols) |= SP_PROT_TLS1_1_CLIENT; + break; + case CURL_SSLVERSION_TLSv1_2: + (*enabled_protocols) |= SP_PROT_TLS1_2_CLIENT; + break; + case CURL_SSLVERSION_TLSv1_3: + + /* Windows Server 2022 and newer */ + if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + (*enabled_protocols) |= SP_PROT_TLS1_3_CLIENT; + break; + } + else { /* Windows 10 and older */ + failf(data, "schannel: TLS 1.3 not supported on Windows prior to 11"); + return CURLE_SSL_CONNECT_ERROR; + } + } + } + return CURLE_OK; +} + +/* longest is 26, buffer is slightly bigger */ +#define LONGEST_ALG_ID 32 +#define CIPHEROPTION(x) {#x, x} + +struct algo { + const char *name; + int id; +}; + +static const struct algo algs[]= { + CIPHEROPTION(CALG_MD2), + CIPHEROPTION(CALG_MD4), + CIPHEROPTION(CALG_MD5), + CIPHEROPTION(CALG_SHA), + CIPHEROPTION(CALG_SHA1), + CIPHEROPTION(CALG_MAC), + CIPHEROPTION(CALG_RSA_SIGN), + CIPHEROPTION(CALG_DSS_SIGN), +/* ifdefs for the options that are defined conditionally in wincrypt.h */ +#ifdef CALG_NO_SIGN + CIPHEROPTION(CALG_NO_SIGN), +#endif + CIPHEROPTION(CALG_RSA_KEYX), + CIPHEROPTION(CALG_DES), +#ifdef CALG_3DES_112 + CIPHEROPTION(CALG_3DES_112), +#endif + CIPHEROPTION(CALG_3DES), + CIPHEROPTION(CALG_DESX), + CIPHEROPTION(CALG_RC2), + CIPHEROPTION(CALG_RC4), + CIPHEROPTION(CALG_SEAL), +#ifdef CALG_DH_SF + CIPHEROPTION(CALG_DH_SF), +#endif + CIPHEROPTION(CALG_DH_EPHEM), +#ifdef CALG_AGREEDKEY_ANY + CIPHEROPTION(CALG_AGREEDKEY_ANY), +#endif +#ifdef CALG_HUGHES_MD5 + CIPHEROPTION(CALG_HUGHES_MD5), +#endif + CIPHEROPTION(CALG_SKIPJACK), +#ifdef CALG_TEK + CIPHEROPTION(CALG_TEK), +#endif + CIPHEROPTION(CALG_CYLINK_MEK), + CIPHEROPTION(CALG_SSL3_SHAMD5), +#ifdef CALG_SSL3_MASTER + CIPHEROPTION(CALG_SSL3_MASTER), +#endif +#ifdef CALG_SCHANNEL_MASTER_HASH + CIPHEROPTION(CALG_SCHANNEL_MASTER_HASH), +#endif +#ifdef CALG_SCHANNEL_MAC_KEY + CIPHEROPTION(CALG_SCHANNEL_MAC_KEY), +#endif +#ifdef CALG_SCHANNEL_ENC_KEY + CIPHEROPTION(CALG_SCHANNEL_ENC_KEY), +#endif +#ifdef CALG_PCT1_MASTER + CIPHEROPTION(CALG_PCT1_MASTER), +#endif +#ifdef CALG_SSL2_MASTER + CIPHEROPTION(CALG_SSL2_MASTER), +#endif +#ifdef CALG_TLS1_MASTER + CIPHEROPTION(CALG_TLS1_MASTER), +#endif +#ifdef CALG_RC5 + CIPHEROPTION(CALG_RC5), +#endif +#ifdef CALG_HMAC + CIPHEROPTION(CALG_HMAC), +#endif +#ifdef CALG_TLS1PRF + CIPHEROPTION(CALG_TLS1PRF), +#endif +#ifdef CALG_HASH_REPLACE_OWF + CIPHEROPTION(CALG_HASH_REPLACE_OWF), +#endif +#ifdef CALG_AES_128 + CIPHEROPTION(CALG_AES_128), +#endif +#ifdef CALG_AES_192 + CIPHEROPTION(CALG_AES_192), +#endif +#ifdef CALG_AES_256 + CIPHEROPTION(CALG_AES_256), +#endif +#ifdef CALG_AES + CIPHEROPTION(CALG_AES), +#endif +#ifdef CALG_SHA_256 + CIPHEROPTION(CALG_SHA_256), +#endif +#ifdef CALG_SHA_384 + CIPHEROPTION(CALG_SHA_384), +#endif +#ifdef CALG_SHA_512 + CIPHEROPTION(CALG_SHA_512), +#endif +#ifdef CALG_ECDH + CIPHEROPTION(CALG_ECDH), +#endif +#ifdef CALG_ECMQV + CIPHEROPTION(CALG_ECMQV), +#endif +#ifdef CALG_ECDSA + CIPHEROPTION(CALG_ECDSA), +#endif +#ifdef CALG_ECDH_EPHEM + CIPHEROPTION(CALG_ECDH_EPHEM), +#endif + {NULL, 0}, +}; + +static int +get_alg_id_by_name(char *name) +{ + char *nameEnd = strchr(name, ':'); + size_t n = nameEnd ? (size_t)(nameEnd - name) : strlen(name); + int i; + + for(i = 0; algs[i].name; i++) { + if((n == strlen(algs[i].name) && !strncmp(algs[i].name, name, n))) + return algs[i].id; + } + return 0; /* not found */ +} + +#define NUM_CIPHERS 47 /* There are 47 options listed above */ + +static CURLcode +set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers, + ALG_ID *algIds) +{ + char *startCur = ciphers; + int algCount = 0; + while(startCur && (0 != *startCur) && (algCount < NUM_CIPHERS)) { + long alg = strtol(startCur, 0, 0); + if(!alg) + alg = get_alg_id_by_name(startCur); + if(alg) + algIds[algCount++] = alg; + else if(!strncmp(startCur, "USE_STRONG_CRYPTO", + sizeof("USE_STRONG_CRYPTO") - 1) || + !strncmp(startCur, "SCH_USE_STRONG_CRYPTO", + sizeof("SCH_USE_STRONG_CRYPTO") - 1)) + schannel_cred->dwFlags |= SCH_USE_STRONG_CRYPTO; + else + return CURLE_SSL_CIPHER; + startCur = strchr(startCur, ':'); + if(startCur) + startCur++; + } + schannel_cred->palgSupportedAlgs = algIds; + schannel_cred->cSupportedAlgs = algCount; + return CURLE_OK; +} + +#ifdef HAS_CLIENT_CERT_PATH + +/* Function allocates memory for store_path only if CURLE_OK is returned */ +static CURLcode +get_cert_location(TCHAR *path, DWORD *store_name, TCHAR **store_path, + TCHAR **thumbprint) +{ + TCHAR *sep; + TCHAR *store_path_start; + size_t store_name_len; + + sep = _tcschr(path, TEXT('\\')); + if(!sep) + return CURLE_SSL_CERTPROBLEM; + + store_name_len = sep - path; + + 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) + *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE; + 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) + *store_name = CERT_SYSTEM_STORE_SERVICES; + 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) + *store_name = CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY; + 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) + *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE; + else + return CURLE_SSL_CERTPROBLEM; + + store_path_start = sep + 1; + + sep = _tcschr(store_path_start, TEXT('\\')); + if(!sep) + return CURLE_SSL_CERTPROBLEM; + + *thumbprint = sep + 1; + if(_tcslen(*thumbprint) != CERT_THUMBPRINT_STR_LEN) + return CURLE_SSL_CERTPROBLEM; + + *sep = TEXT('\0'); + *store_path = _tcsdup(store_path_start); + *sep = TEXT('\\'); + if(!*store_path) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} +#endif +static CURLcode +schannel_acquire_credential_handle(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + +#ifdef HAS_CLIENT_CERT_PATH + PCCERT_CONTEXT client_certs[1] = { NULL }; + HCERTSTORE client_cert_store = NULL; +#endif + SECURITY_STATUS sspi_status = SEC_E_OK; + CURLcode result; + + /* setup Schannel API options */ + DWORD flags = 0; + DWORD enabled_protocols = 0; + + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)(connssl->backend); + + DEBUGASSERT(backend); + + if(conn_config->verifypeer) { +#ifdef HAS_MANUAL_VERIFY_API + if(backend->use_manual_cred_validation) + flags = SCH_CRED_MANUAL_CRED_VALIDATION; + else +#endif + flags = SCH_CRED_AUTO_CRED_VALIDATION; + + if(ssl_config->no_revoke) { + flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | + SCH_CRED_IGNORE_REVOCATION_OFFLINE; + + DEBUGF(infof(data, "schannel: disabled server certificate revocation " + "checks")); + } + else if(ssl_config->revoke_best_effort) { + flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | + SCH_CRED_IGNORE_REVOCATION_OFFLINE | SCH_CRED_REVOCATION_CHECK_CHAIN; + + DEBUGF(infof(data, "schannel: ignore revocation offline errors")); + } + else { + flags |= SCH_CRED_REVOCATION_CHECK_CHAIN; + + DEBUGF(infof(data, + "schannel: checking server certificate revocation")); + } + } + else { + flags = SCH_CRED_MANUAL_CRED_VALIDATION | + SCH_CRED_IGNORE_NO_REVOCATION_CHECK | + SCH_CRED_IGNORE_REVOCATION_OFFLINE; + DEBUGF(infof(data, + "schannel: disabled server cert revocation checks")); + } + + if(!conn_config->verifyhost) { + flags |= SCH_CRED_NO_SERVERNAME_CHECK; + DEBUGF(infof(data, "schannel: verifyhost setting prevents Schannel from " + "comparing the supplied target name with the subject " + "names in server certificates.")); + } + + if(!ssl_config->auto_client_cert) { + flags &= ~SCH_CRED_USE_DEFAULT_CREDS; + flags |= SCH_CRED_NO_DEFAULT_CREDS; + infof(data, "schannel: disabled automatic use of client certificate"); + } + else + infof(data, "schannel: enabled automatic use of client certificate"); + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + case CURL_SSLVERSION_TLSv1_3: + { + result = schannel_set_ssl_version_min_max(&enabled_protocols, cf, data); + if(result != CURLE_OK) + return result; + break; + } + case CURL_SSLVERSION_SSLv3: + case CURL_SSLVERSION_SSLv2: + failf(data, "SSL versions not supported"); + return CURLE_NOT_BUILT_IN; + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + +#ifdef HAS_CLIENT_CERT_PATH + /* client certificate */ + if(data->set.ssl.primary.clientcert || data->set.ssl.primary.cert_blob) { + DWORD cert_store_name = 0; + TCHAR *cert_store_path = NULL; + TCHAR *cert_thumbprint_str = NULL; + CRYPT_HASH_BLOB cert_thumbprint; + BYTE cert_thumbprint_data[CERT_THUMBPRINT_DATA_LEN]; + HCERTSTORE cert_store = NULL; + FILE *fInCert = NULL; + void *certdata = NULL; + size_t certsize = 0; + bool blob = data->set.ssl.primary.cert_blob != NULL; + TCHAR *cert_path = NULL; + if(blob) { + certdata = data->set.ssl.primary.cert_blob->data; + certsize = data->set.ssl.primary.cert_blob->len; + } + else { + cert_path = curlx_convert_UTF8_to_tchar( + data->set.ssl.primary.clientcert); + if(!cert_path) + return CURLE_OUT_OF_MEMORY; + + result = get_cert_location(cert_path, &cert_store_name, + &cert_store_path, &cert_thumbprint_str); + + if(result && (data->set.ssl.primary.clientcert[0]!='\0')) + fInCert = fopen(data->set.ssl.primary.clientcert, "rb"); + + if(result && !fInCert) { + failf(data, "schannel: Failed to get certificate location" + " or file for %s", + data->set.ssl.primary.clientcert); + curlx_unicodefree(cert_path); + return result; + } + } + + if((fInCert || blob) && (data->set.ssl.cert_type) && + (!strcasecompare(data->set.ssl.cert_type, "P12"))) { + failf(data, "schannel: certificate format compatibility error " + " for %s", + blob ? "(memory blob)" : data->set.ssl.primary.clientcert); + curlx_unicodefree(cert_path); + return CURLE_SSL_CERTPROBLEM; + } + + if(fInCert || blob) { + /* Reading a .P12 or .pfx file, like the example at bottom of + https://social.msdn.microsoft.com/Forums/windowsdesktop/ + en-US/3e7bc95f-b21a-4bcd-bd2c-7f996718cae5 + */ + CRYPT_DATA_BLOB datablob; + WCHAR* pszPassword; + size_t pwd_len = 0; + int str_w_len = 0; + const char *cert_showfilename_error = blob ? + "(memory blob)" : data->set.ssl.primary.clientcert; + curlx_unicodefree(cert_path); + if(fInCert) { + long cert_tell = 0; + bool continue_reading = fseek(fInCert, 0, SEEK_END) == 0; + if(continue_reading) + cert_tell = ftell(fInCert); + 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) + certdata = malloc(certsize + 1); + if((!certdata) || + ((int) fread(certdata, certsize, 1, fInCert) != 1)) + continue_reading = FALSE; + fclose(fInCert); + if(!continue_reading) { + failf(data, "schannel: Failed to read cert file %s", + data->set.ssl.primary.clientcert); + free(certdata); + return CURLE_SSL_CERTPROBLEM; + } + } + + /* Convert key-pair data to the in-memory certificate store */ + datablob.pbData = (BYTE*)certdata; + datablob.cbData = (DWORD)certsize; + + if(data->set.ssl.key_passwd) + pwd_len = strlen(data->set.ssl.key_passwd); + pszPassword = (WCHAR*)malloc(sizeof(WCHAR)*(pwd_len + 1)); + if(pszPassword) { + if(pwd_len > 0) + str_w_len = MultiByteToWideChar(CP_UTF8, + MB_ERR_INVALID_CHARS, + data->set.ssl.key_passwd, + (int)pwd_len, + pszPassword, (int)(pwd_len + 1)); + + if((str_w_len >= 0) && (str_w_len <= (int)pwd_len)) + pszPassword[str_w_len] = 0; + else + pszPassword[0] = 0; + + if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) + cert_store = PFXImportCertStore(&datablob, pszPassword, + PKCS12_NO_PERSIST_KEY); + else + cert_store = PFXImportCertStore(&datablob, pszPassword, 0); + + free(pszPassword); + } + if(!blob) + free(certdata); + if(!cert_store) { + DWORD errorcode = GetLastError(); + if(errorcode == ERROR_INVALID_PASSWORD) + failf(data, "schannel: Failed to import cert file %s, " + "password is bad", + cert_showfilename_error); + else + failf(data, "schannel: Failed to import cert file %s, " + "last error is 0x%x", + cert_showfilename_error, errorcode); + return CURLE_SSL_CERTPROBLEM; + } + + client_certs[0] = CertFindCertificateInStore( + cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, + CERT_FIND_ANY, NULL, NULL); + + if(!client_certs[0]) { + failf(data, "schannel: Failed to get certificate from file %s" + ", last error is 0x%x", + cert_showfilename_error, GetLastError()); + CertCloseStore(cert_store, 0); + return CURLE_SSL_CERTPROBLEM; + } + } + else { + cert_store = + CertOpenStore(CURL_CERT_STORE_PROV_SYSTEM, 0, + (HCRYPTPROV)NULL, + CERT_STORE_OPEN_EXISTING_FLAG | cert_store_name, + cert_store_path); + if(!cert_store) { + failf(data, "schannel: Failed to open cert store %x %s, " + "last error is 0x%x", + cert_store_name, cert_store_path, GetLastError()); + free(cert_store_path); + curlx_unicodefree(cert_path); + return CURLE_SSL_CERTPROBLEM; + } + free(cert_store_path); + + cert_thumbprint.pbData = cert_thumbprint_data; + cert_thumbprint.cbData = CERT_THUMBPRINT_DATA_LEN; + + if(!CryptStringToBinary(cert_thumbprint_str, + CERT_THUMBPRINT_STR_LEN, + CRYPT_STRING_HEX, + cert_thumbprint_data, + &cert_thumbprint.cbData, + NULL, NULL)) { + curlx_unicodefree(cert_path); + CertCloseStore(cert_store, 0); + return CURLE_SSL_CERTPROBLEM; + } + + client_certs[0] = CertFindCertificateInStore( + cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, + CERT_FIND_HASH, &cert_thumbprint, NULL); + + curlx_unicodefree(cert_path); + + if(!client_certs[0]) { + /* CRYPT_E_NOT_FOUND / E_INVALIDARG */ + CertCloseStore(cert_store, 0); + return CURLE_SSL_CERTPROBLEM; + } + } + client_cert_store = cert_store; + } +#else + if(data->set.ssl.primary.clientcert || data->set.ssl.primary.cert_blob) { + failf(data, "schannel: client cert support not built in"); + return CURLE_NOT_BUILT_IN; + } +#endif + + /* allocate memory for the reusable credential handle */ + backend->cred = (struct Curl_schannel_cred *) + calloc(1, sizeof(struct Curl_schannel_cred)); + if(!backend->cred) { + failf(data, "schannel: unable to allocate memory"); + +#ifdef HAS_CLIENT_CERT_PATH + if(client_certs[0]) + CertFreeCertificateContext(client_certs[0]); + if(client_cert_store) + CertCloseStore(client_cert_store, 0); +#endif + + return CURLE_OUT_OF_MEMORY; + } + backend->cred->refcount = 1; + +#ifdef HAS_CLIENT_CERT_PATH + /* Since we did not persist the key, we need to extend the store's + * lifetime until the end of the connection + */ + backend->cred->client_cert_store = client_cert_store; +#endif + + /* 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). */ + if(!conn_config->cipher_list && + curlx_verify_windows_version(10, 0, 17763, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + + char *ciphers13 = 0; + + bool disable_aes_gcm_sha384 = FALSE; + bool disable_aes_gcm_sha256 = FALSE; + bool disable_chacha_poly = FALSE; + bool disable_aes_ccm_8_sha256 = FALSE; + bool disable_aes_ccm_sha256 = FALSE; + + SCH_CREDENTIALS credentials = { 0 }; + TLS_PARAMETERS tls_parameters = { 0 }; + CRYPTO_SETTINGS crypto_settings[4] = { { 0 } }; + UNICODE_STRING blocked_ccm_modes[1] = { { 0 } }; + UNICODE_STRING blocked_gcm_modes[1] = { { 0 } }; + + int crypto_settings_idx = 0; + + + /* If TLS 1.3 ciphers are explicitly listed, then + * disable all the ciphers and re-enable which + * ciphers the user has provided. + */ + ciphers13 = conn_config->cipher_list13; + if(ciphers13) { + const int remaining_ciphers = 5; + + /* detect which remaining ciphers to enable + and then disable everything else. + */ + + char *startCur = ciphers13; + int algCount = 0; + char tmp[LONGEST_ALG_ID] = { 0 }; + char *nameEnd; + size_t n; + + disable_aes_gcm_sha384 = TRUE; + disable_aes_gcm_sha256 = TRUE; + disable_chacha_poly = TRUE; + disable_aes_ccm_8_sha256 = TRUE; + disable_aes_ccm_sha256 = TRUE; + + while(startCur && (0 != *startCur) && (algCount < remaining_ciphers)) { + nameEnd = strchr(startCur, ':'); + n = nameEnd ? (size_t)(nameEnd - startCur) : strlen(startCur); + + /* reject too-long cipher names */ + if(n > (LONGEST_ALG_ID - 1)) { + failf(data, "schannel: Cipher name too long, not checked"); + return CURLE_SSL_CIPHER; + } + + strncpy(tmp, startCur, n); + tmp[n] = 0; + + if(disable_aes_gcm_sha384 + && !strcmp("TLS_AES_256_GCM_SHA384", tmp)) { + disable_aes_gcm_sha384 = FALSE; + } + else if(disable_aes_gcm_sha256 + && !strcmp("TLS_AES_128_GCM_SHA256", tmp)) { + disable_aes_gcm_sha256 = FALSE; + } + else if(disable_chacha_poly + && !strcmp("TLS_CHACHA20_POLY1305_SHA256", tmp)) { + disable_chacha_poly = FALSE; + } + else if(disable_aes_ccm_8_sha256 + && !strcmp("TLS_AES_128_CCM_8_SHA256", tmp)) { + disable_aes_ccm_8_sha256 = FALSE; + } + else if(disable_aes_ccm_sha256 + && !strcmp("TLS_AES_128_CCM_SHA256", tmp)) { + disable_aes_ccm_sha256 = FALSE; + } + else { + failf(data, "schannel: Unknown TLS 1.3 cipher: %s", tmp); + return CURLE_SSL_CIPHER; + } + + startCur = nameEnd; + if(startCur) + startCur++; + + algCount++; + } + } + + if(disable_aes_gcm_sha384 && disable_aes_gcm_sha256 + && disable_chacha_poly && disable_aes_ccm_8_sha256 + && disable_aes_ccm_sha256) { + failf(data, "schannel: All available TLS 1.3 ciphers were disabled"); + return CURLE_SSL_CIPHER; + } + + /* Disable TLS_AES_128_CCM_8_SHA256 and/or TLS_AES_128_CCM_SHA256 */ + if(disable_aes_ccm_8_sha256 || disable_aes_ccm_sha256) { + /* + Disallow AES_CCM algorithm. + */ + blocked_ccm_modes[0].Length = sizeof(BCRYPT_CHAIN_MODE_CCM); + blocked_ccm_modes[0].MaximumLength = sizeof(BCRYPT_CHAIN_MODE_CCM); + blocked_ccm_modes[0].Buffer = (PWSTR)BCRYPT_CHAIN_MODE_CCM; + + crypto_settings[crypto_settings_idx].eAlgorithmUsage = + TlsParametersCngAlgUsageCipher; + crypto_settings[crypto_settings_idx].rgstrChainingModes = + blocked_ccm_modes; + crypto_settings[crypto_settings_idx].cChainingModes = + ARRAYSIZE(blocked_ccm_modes); + crypto_settings[crypto_settings_idx].strCngAlgId.Length = + sizeof(BCRYPT_AES_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = + sizeof(BCRYPT_AES_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = + (PWSTR)BCRYPT_AES_ALGORITHM; + + /* only disabling one of the CCM modes */ + if(disable_aes_ccm_8_sha256 != disable_aes_ccm_sha256) { + if(disable_aes_ccm_8_sha256) + crypto_settings[crypto_settings_idx].dwMinBitLength = 128; + else /* disable_aes_ccm_sha256 */ + crypto_settings[crypto_settings_idx].dwMaxBitLength = 64; + } + + crypto_settings_idx++; + } + + /* Disable TLS_AES_256_GCM_SHA384 and/or TLS_AES_128_GCM_SHA256 */ + if(disable_aes_gcm_sha384 || disable_aes_gcm_sha256) { + + /* + Disallow AES_GCM algorithm + */ + blocked_gcm_modes[0].Length = sizeof(BCRYPT_CHAIN_MODE_GCM); + blocked_gcm_modes[0].MaximumLength = sizeof(BCRYPT_CHAIN_MODE_GCM); + blocked_gcm_modes[0].Buffer = (PWSTR)BCRYPT_CHAIN_MODE_GCM; + + /* if only one is disabled, then explicitly disable the + digest cipher suite (sha384 or sha256) */ + if(disable_aes_gcm_sha384 != disable_aes_gcm_sha256) { + crypto_settings[crypto_settings_idx].eAlgorithmUsage = + TlsParametersCngAlgUsageDigest; + crypto_settings[crypto_settings_idx].strCngAlgId.Length = + sizeof(disable_aes_gcm_sha384 ? + BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = + sizeof(disable_aes_gcm_sha384 ? + BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = + (PWSTR)(disable_aes_gcm_sha384 ? + BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); + } + else { /* Disable both AES_GCM ciphers */ + crypto_settings[crypto_settings_idx].eAlgorithmUsage = + TlsParametersCngAlgUsageCipher; + crypto_settings[crypto_settings_idx].strCngAlgId.Length = + sizeof(BCRYPT_AES_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = + sizeof(BCRYPT_AES_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = + (PWSTR)BCRYPT_AES_ALGORITHM; + } + + crypto_settings[crypto_settings_idx].rgstrChainingModes = + blocked_gcm_modes; + crypto_settings[crypto_settings_idx].cChainingModes = 1; + + crypto_settings_idx++; + } + + /* + Disable ChaCha20-Poly1305. + */ + if(disable_chacha_poly) { + crypto_settings[crypto_settings_idx].eAlgorithmUsage = + TlsParametersCngAlgUsageCipher; + crypto_settings[crypto_settings_idx].strCngAlgId.Length = + sizeof(BCRYPT_CHACHA20_POLY1305_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = + sizeof(BCRYPT_CHACHA20_POLY1305_ALGORITHM); + crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = + (PWSTR)BCRYPT_CHACHA20_POLY1305_ALGORITHM; + crypto_settings_idx++; + } + + tls_parameters.pDisabledCrypto = crypto_settings; + + /* The number of blocked suites */ + tls_parameters.cDisabledCrypto = crypto_settings_idx; + credentials.pTlsParameters = &tls_parameters; + credentials.cTlsParameters = 1; + + credentials.dwVersion = SCH_CREDENTIALS_VERSION; + credentials.dwFlags = flags | SCH_USE_STRONG_CRYPTO; + + credentials.pTlsParameters->grbitDisabledProtocols = + (DWORD)~enabled_protocols; + +#ifdef HAS_CLIENT_CERT_PATH + if(client_certs[0]) { + credentials.cCreds = 1; + credentials.paCred = client_certs; + } +#endif + + sspi_status = + s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, + SECPKG_CRED_OUTBOUND, NULL, + &credentials, NULL, NULL, + &backend->cred->cred_handle, + &backend->cred->time_stamp); + } + else { + /* Pre-Windows 10 1809 or the user set a legacy algorithm list. Although MS + doesn't document it, currently Schannel will not negotiate TLS 1.3 when + SCHANNEL_CRED is used. */ + ALG_ID algIds[NUM_CIPHERS]; + char *ciphers = conn_config->cipher_list; + SCHANNEL_CRED schannel_cred = { 0 }; + schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; + schannel_cred.dwFlags = flags; + schannel_cred.grbitEnabledProtocols = enabled_protocols; + + if(ciphers) { + if((enabled_protocols & SP_PROT_TLS1_3_CLIENT)) { + infof(data, "schannel: WARNING: This version of Schannel may " + "negotiate a less-secure TLS version than TLS 1.3 because the " + "user set an algorithm cipher list."); + } + if(conn_config->cipher_list13) { + failf(data, "schannel: This version of Schannel does not support " + "setting an algorithm cipher list and TLS 1.3 cipher list at " + "the same time"); + return CURLE_SSL_CIPHER; + } + result = set_ssl_ciphers(&schannel_cred, ciphers, algIds); + if(CURLE_OK != result) { + failf(data, "schannel: Failed setting algorithm cipher list"); + return result; + } + } + else { + schannel_cred.dwFlags = flags | SCH_USE_STRONG_CRYPTO; + } + +#ifdef HAS_CLIENT_CERT_PATH + if(client_certs[0]) { + schannel_cred.cCreds = 1; + schannel_cred.paCred = client_certs; + } +#endif + + sspi_status = + s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, + SECPKG_CRED_OUTBOUND, NULL, + &schannel_cred, NULL, NULL, + &backend->cred->cred_handle, + &backend->cred->time_stamp); + } + +#ifdef HAS_CLIENT_CERT_PATH + if(client_certs[0]) + CertFreeCertificateContext(client_certs[0]); +#endif + + if(sspi_status != SEC_E_OK) { + char buffer[STRERROR_LEN]; + failf(data, "schannel: AcquireCredentialsHandle failed: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + Curl_safefree(backend->cred); + switch(sspi_status) { + case SEC_E_INSUFFICIENT_MEMORY: + return CURLE_OUT_OF_MEMORY; + case SEC_E_NO_CREDENTIALS: + case SEC_E_SECPKG_NOT_FOUND: + case SEC_E_NOT_OWNER: + case SEC_E_UNKNOWN_CREDENTIALS: + case SEC_E_INTERNAL_ERROR: + default: + return CURLE_SSL_CONNECT_ERROR; + } + } + + return CURLE_OK; +} + +static CURLcode +schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + ssize_t written = -1; + struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + SecBuffer outbuf; + SecBufferDesc outbuf_desc; + SecBuffer inbuf; + SecBufferDesc inbuf_desc; +#ifdef HAS_ALPN + unsigned char alpn_buffer[128]; +#endif + SECURITY_STATUS sspi_status = SEC_E_OK; + struct Curl_schannel_cred *old_cred = NULL; + CURLcode result; + + DEBUGASSERT(backend); + DEBUGF(infof(data, + "schannel: SSL/TLS connection with %s port %d (step 1/3)", + connssl->peer.hostname, connssl->port)); + + if(curlx_verify_windows_version(5, 1, 0, PLATFORM_WINNT, + VERSION_LESS_THAN_EQUAL)) { + /* Schannel in Windows XP (OS version 5.1) uses legacy handshakes and + algorithms that may not be supported by all servers. */ + infof(data, "schannel: Windows version is old and may not be able to " + "connect to some servers due to lack of SNI, algorithms, etc."); + } + +#ifdef HAS_ALPN + /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above. + Also it doesn't seem to be supported for Wine, see curl bug #983. */ + backend->use_alpn = connssl->alpn && + !GetProcAddress(GetModuleHandle(TEXT("ntdll")), + "wine_get_version") && + curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL); +#else + backend->use_alpn = false; +#endif + +#ifdef _WIN32_WCE +#ifdef HAS_MANUAL_VERIFY_API + /* certificate validation on CE doesn't seem to work right; we'll + * do it following a more manual process. */ + backend->use_manual_cred_validation = true; +#else +#error "compiler too old to support requisite manual cert verify for Win CE" +#endif +#else +#ifdef HAS_MANUAL_VERIFY_API + if(conn_config->CAfile || conn_config->ca_info_blob) { + if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + backend->use_manual_cred_validation = true; + } + else { + failf(data, "schannel: this version of Windows is too old to support " + "certificate verification via CA bundle file."); + return CURLE_SSL_CACERT_BADFILE; + } + } + else + backend->use_manual_cred_validation = false; +#else + if(conn_config->CAfile || conn_config->ca_info_blob) { + failf(data, "schannel: CA cert support not built in"); + return CURLE_NOT_BUILT_IN; + } +#endif +#endif + + backend->cred = NULL; + + /* check for an existing reusable credential handle */ + if(ssl_config->primary.sessionid) { + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, (void **)&old_cred, NULL)) { + backend->cred = old_cred; + DEBUGF(infof(data, "schannel: reusing existing credential handle")); + + /* increment the reference counter of the credential/session handle */ + backend->cred->refcount++; + DEBUGF(infof(data, + "schannel: incremented credential handle refcount = %d", + backend->cred->refcount)); + } + Curl_ssl_sessionid_unlock(data); + } + + if(!backend->cred) { + char *snihost; + result = schannel_acquire_credential_handle(cf, data); + if(result) + return result; + /* schannel_acquire_credential_handle() sets backend->cred accordingly or + it returns error otherwise. */ + + /* A hostname associated with the credential is needed by + InitializeSecurityContext for SNI and other reasons. */ + snihost = connssl->peer.sni? connssl->peer.sni : connssl->peer.hostname; + backend->cred->sni_hostname = curlx_convert_UTF8_to_tchar(snihost); + if(!backend->cred->sni_hostname) + return CURLE_OUT_OF_MEMORY; + } + + /* Warn if SNI is disabled due to use of an IP address */ + if(connssl->peer.is_ip_address) { + infof(data, "schannel: using IP address, SNI is not supported by OS."); + } + +#ifdef HAS_ALPN + if(backend->use_alpn) { + int cur = 0; + int list_start_index = 0; + unsigned int *extension_len = NULL; + unsigned short* list_len = NULL; + struct alpn_proto_buf proto; + + /* The first four bytes will be an unsigned int indicating number + of bytes of data in the rest of the buffer. */ + extension_len = (unsigned int *)(void *)(&alpn_buffer[cur]); + cur += (int)sizeof(unsigned int); + + /* The next four bytes are an indicator that this buffer will contain + ALPN data, as opposed to NPN, for example. */ + *(unsigned int *)(void *)&alpn_buffer[cur] = + SecApplicationProtocolNegotiationExt_ALPN; + cur += (int)sizeof(unsigned int); + + /* The next two bytes will be an unsigned short indicating the number + of bytes used to list the preferred protocols. */ + list_len = (unsigned short*)(void *)(&alpn_buffer[cur]); + cur += (int)sizeof(unsigned short); + + list_start_index = cur; + + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result) { + failf(data, "Error setting ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + memcpy(&alpn_buffer[cur], proto.data, proto.len); + cur += proto.len; + + *list_len = curlx_uitous(cur - list_start_index); + *extension_len = *list_len + + (unsigned short)sizeof(unsigned int) + + (unsigned short)sizeof(unsigned short); + + InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur); + InitSecBufferDesc(&inbuf_desc, &inbuf, 1); + + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } + else { + InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); + InitSecBufferDesc(&inbuf_desc, &inbuf, 1); + } +#else /* HAS_ALPN */ + InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); + InitSecBufferDesc(&inbuf_desc, &inbuf, 1); +#endif + + /* setup output buffer */ + InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); + InitSecBufferDesc(&outbuf_desc, &outbuf, 1); + + /* security request flags */ + backend->req_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_STREAM; + + if(!ssl_config->auto_client_cert) { + backend->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS; + } + + /* allocate memory for the security context handle */ + backend->ctxt = (struct Curl_schannel_ctxt *) + calloc(1, sizeof(struct Curl_schannel_ctxt)); + if(!backend->ctxt) { + failf(data, "schannel: unable to allocate memory"); + return CURLE_OUT_OF_MEMORY; + } + + /* Schannel InitializeSecurityContext: + https://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx + + At the moment we don't pass inbuf unless we're 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 = s_pSecFn->InitializeSecurityContext( + &backend->cred->cred_handle, NULL, backend->cred->sni_hostname, + backend->req_flags, 0, 0, + (backend->use_alpn ? &inbuf_desc : NULL), + 0, &backend->ctxt->ctxt_handle, + &outbuf_desc, &backend->ret_flags, &backend->ctxt->time_stamp); + + if(sspi_status != SEC_I_CONTINUE_NEEDED) { + char buffer[STRERROR_LEN]; + Curl_safefree(backend->ctxt); + switch(sspi_status) { + case SEC_E_INSUFFICIENT_MEMORY: + failf(data, "schannel: initial InitializeSecurityContext failed: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + return CURLE_OUT_OF_MEMORY; + case SEC_E_WRONG_PRINCIPAL: + failf(data, "schannel: SNI or certificate check failed: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + return CURLE_PEER_FAILED_VERIFICATION; + /* + case SEC_E_INVALID_HANDLE: + case SEC_E_INVALID_TOKEN: + case SEC_E_LOGON_DENIED: + case SEC_E_TARGET_UNKNOWN: + case SEC_E_NO_AUTHENTICATING_AUTHORITY: + case SEC_E_INTERNAL_ERROR: + case SEC_E_NO_CREDENTIALS: + case SEC_E_UNSUPPORTED_FUNCTION: + case SEC_E_APPLICATION_PROTOCOL_MISMATCH: + */ + default: + failf(data, "schannel: initial InitializeSecurityContext failed: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + return CURLE_SSL_CONNECT_ERROR; + } + } + + DEBUGF(infof(data, "schannel: sending initial handshake data: " + "sending %lu bytes.", outbuf.cbBuffer)); + + /* send initial handshake data which is now stored in output buffer */ + written = Curl_conn_cf_send(cf->next, data, + outbuf.pvBuffer, outbuf.cbBuffer, + &result); + s_pSecFn->FreeContextBuffer(outbuf.pvBuffer); + if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) { + failf(data, "schannel: failed to send initial handshake data: " + "sent %zd of %lu bytes", written, outbuf.cbBuffer); + return CURLE_SSL_CONNECT_ERROR; + } + + DEBUGF(infof(data, "schannel: sent initial handshake data: " + "sent %zd bytes", written)); + + backend->recv_unrecoverable_err = CURLE_OK; + backend->recv_sspi_close_notify = false; + backend->recv_connection_closed = false; + backend->recv_renegotiating = false; + backend->encdata_is_incomplete = false; + + /* continue to second handshake step */ + connssl->connecting_state = ssl_connect_2; + + return CURLE_OK; +} + +static CURLcode +schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + int i; + ssize_t nread = -1, written = -1; + unsigned char *reallocated_buffer; + SecBuffer outbuf[3]; + SecBufferDesc outbuf_desc; + SecBuffer inbuf[2]; + SecBufferDesc inbuf_desc; + SECURITY_STATUS sspi_status = SEC_E_OK; + CURLcode result; + bool doread; + const char *pubkey_ptr; + + DEBUGASSERT(backend); + + doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE; + + DEBUGF(infof(data, + "schannel: SSL/TLS connection with %s port %d (step 2/3)", + connssl->peer.hostname, connssl->port)); + + if(!backend->cred || !backend->ctxt) + return CURLE_SSL_CONNECT_ERROR; + + /* buffer to store previously received and decrypted data */ + if(!backend->decdata_buffer) { + backend->decdata_offset = 0; + backend->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE; + backend->decdata_buffer = malloc(backend->decdata_length); + if(!backend->decdata_buffer) { + failf(data, "schannel: unable to allocate memory"); + return CURLE_OUT_OF_MEMORY; + } + } + + /* buffer to store previously received and encrypted data */ + if(!backend->encdata_buffer) { + backend->encdata_is_incomplete = false; + backend->encdata_offset = 0; + backend->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE; + backend->encdata_buffer = malloc(backend->encdata_length); + if(!backend->encdata_buffer) { + failf(data, "schannel: unable to allocate memory"); + return CURLE_OUT_OF_MEMORY; + } + } + + /* if we need a bigger buffer to read a full message, increase buffer now */ + if(backend->encdata_length - backend->encdata_offset < + CURL_SCHANNEL_BUFFER_FREE_SIZE) { + /* increase internal encrypted data buffer */ + size_t reallocated_length = backend->encdata_offset + + CURL_SCHANNEL_BUFFER_FREE_SIZE; + reallocated_buffer = realloc(backend->encdata_buffer, + reallocated_length); + + if(!reallocated_buffer) { + failf(data, "schannel: unable to re-allocate memory"); + return CURLE_OUT_OF_MEMORY; + } + else { + backend->encdata_buffer = reallocated_buffer; + backend->encdata_length = reallocated_length; + } + } + + for(;;) { + if(doread) { + /* read encrypted handshake data from socket */ + nread = Curl_conn_cf_recv(cf->next, data, + (char *) (backend->encdata_buffer + + backend->encdata_offset), + backend->encdata_length - + backend->encdata_offset, + &result); + if(result == CURLE_AGAIN) { + if(connssl->connecting_state != ssl_connect_2_writing) + connssl->connecting_state = ssl_connect_2_reading; + DEBUGF(infof(data, "schannel: failed to receive handshake, " + "need more data")); + return CURLE_OK; + } + else if((result != CURLE_OK) || (nread == 0)) { + failf(data, "schannel: failed to receive handshake, " + "SSL/TLS connection failed"); + return CURLE_SSL_CONNECT_ERROR; + } + + /* increase encrypted data buffer offset */ + backend->encdata_offset += nread; + backend->encdata_is_incomplete = false; + DEBUGF(infof(data, "schannel: encrypted data got %zd", nread)); + } + + DEBUGF(infof(data, + "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); + + /* setup input buffers */ + InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(backend->encdata_offset), + curlx_uztoul(backend->encdata_offset)); + InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0); + InitSecBufferDesc(&inbuf_desc, inbuf, 2); + + /* setup output buffers */ + InitSecBuffer(&outbuf[0], SECBUFFER_TOKEN, NULL, 0); + InitSecBuffer(&outbuf[1], SECBUFFER_ALERT, NULL, 0); + InitSecBuffer(&outbuf[2], SECBUFFER_EMPTY, NULL, 0); + InitSecBufferDesc(&outbuf_desc, outbuf, 3); + + if(!inbuf[0].pvBuffer) { + failf(data, "schannel: unable to allocate memory"); + return CURLE_OUT_OF_MEMORY; + } + + /* copy received handshake data into input buffer */ + memcpy(inbuf[0].pvBuffer, backend->encdata_buffer, + backend->encdata_offset); + + sspi_status = s_pSecFn->InitializeSecurityContext( + &backend->cred->cred_handle, &backend->ctxt->ctxt_handle, + backend->cred->sni_hostname, backend->req_flags, + 0, 0, &inbuf_desc, 0, NULL, + &outbuf_desc, &backend->ret_flags, &backend->ctxt->time_stamp); + + /* free buffer for received handshake data */ + Curl_safefree(inbuf[0].pvBuffer); + + /* check if the handshake was incomplete */ + if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) { + backend->encdata_is_incomplete = true; + connssl->connecting_state = ssl_connect_2_reading; + DEBUGF(infof(data, + "schannel: received incomplete message, need more data")); + return CURLE_OK; + } + + /* If the server has requested a client certificate, attempt to continue + the handshake without one. This will allow connections to servers which + request a client certificate but do not require it. */ + if(sspi_status == SEC_I_INCOMPLETE_CREDENTIALS && + !(backend->req_flags & ISC_REQ_USE_SUPPLIED_CREDS)) { + backend->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS; + connssl->connecting_state = ssl_connect_2_writing; + DEBUGF(infof(data, + "schannel: a client certificate has been requested")); + return CURLE_OK; + } + + /* check if the handshake needs to be continued */ + if(sspi_status == SEC_I_CONTINUE_NEEDED || sspi_status == SEC_E_OK) { + for(i = 0; i < 3; i++) { + /* search for handshake tokens that need to be send */ + if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) { + DEBUGF(infof(data, "schannel: sending next handshake data: " + "sending %lu bytes.", outbuf[i].cbBuffer)); + + /* send handshake token to server */ + written = Curl_conn_cf_send(cf->next, data, + outbuf[i].pvBuffer, outbuf[i].cbBuffer, + &result); + if((result != CURLE_OK) || + (outbuf[i].cbBuffer != (size_t) written)) { + failf(data, "schannel: failed to send next handshake data: " + "sent %zd of %lu bytes", written, outbuf[i].cbBuffer); + return CURLE_SSL_CONNECT_ERROR; + } + } + + /* free obsolete buffer */ + if(outbuf[i].pvBuffer) { + s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer); + } + } + } + else { + char buffer[STRERROR_LEN]; + switch(sspi_status) { + case SEC_E_INSUFFICIENT_MEMORY: + failf(data, "schannel: next InitializeSecurityContext failed: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + return CURLE_OUT_OF_MEMORY; + case SEC_E_WRONG_PRINCIPAL: + failf(data, "schannel: SNI or certificate check failed: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + return CURLE_PEER_FAILED_VERIFICATION; + case SEC_E_UNTRUSTED_ROOT: + failf(data, "schannel: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + return CURLE_PEER_FAILED_VERIFICATION; + /* + case SEC_E_INVALID_HANDLE: + case SEC_E_INVALID_TOKEN: + case SEC_E_LOGON_DENIED: + case SEC_E_TARGET_UNKNOWN: + case SEC_E_NO_AUTHENTICATING_AUTHORITY: + case SEC_E_INTERNAL_ERROR: + case SEC_E_NO_CREDENTIALS: + case SEC_E_UNSUPPORTED_FUNCTION: + case SEC_E_APPLICATION_PROTOCOL_MISMATCH: + */ + default: + failf(data, "schannel: next InitializeSecurityContext failed: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + return CURLE_SSL_CONNECT_ERROR; + } + } + + /* check if there was additional remaining encrypted data */ + if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) { + DEBUGF(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're 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. + */ + /* 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) { + memmove(backend->encdata_buffer, + (backend->encdata_buffer + backend->encdata_offset) - + inbuf[1].cbBuffer, inbuf[1].cbBuffer); + backend->encdata_offset = inbuf[1].cbBuffer; + if(sspi_status == SEC_I_CONTINUE_NEEDED) { + doread = FALSE; + continue; + } + } + } + else { + backend->encdata_offset = 0; + } + break; + } + + /* check if the handshake needs to be continued */ + if(sspi_status == SEC_I_CONTINUE_NEEDED) { + connssl->connecting_state = ssl_connect_2_reading; + return CURLE_OK; + } + + /* check if the handshake is complete */ + if(sspi_status == SEC_E_OK) { + connssl->connecting_state = ssl_connect_3; + DEBUGF(infof(data, "schannel: SSL/TLS handshake complete")); + } + + pubkey_ptr = Curl_ssl_cf_is_proxy(cf)? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + if(pubkey_ptr) { + result = schannel_pkp_pin_peer_pubkey(cf, data, pubkey_ptr); + if(result) { + failf(data, "SSL: public key does not match pinned public key"); + return result; + } + } + +#ifdef HAS_MANUAL_VERIFY_API + if(conn_config->verifypeer && backend->use_manual_cred_validation) { + /* Certificate verification also verifies the hostname if verifyhost */ + return Curl_verify_certificate(cf, data); + } +#endif + + /* Verify the hostname manually when certificate verification is disabled, + because in that case Schannel won't verify it. */ + if(!conn_config->verifypeer && conn_config->verifyhost) + return Curl_verify_host(cf, data); + + return CURLE_OK; +} + +static bool +valid_cert_encoding(const CERT_CONTEXT *cert_context) +{ + return (cert_context != NULL) && + ((cert_context->dwCertEncodingType & X509_ASN_ENCODING) != 0) && + (cert_context->pbCertEncoded != NULL) && + (cert_context->cbCertEncoded > 0); +} + +typedef bool(*Read_crt_func)(const CERT_CONTEXT *ccert_context, + bool reverse_order, void *arg); + +static void +traverse_cert_store(const CERT_CONTEXT *context, Read_crt_func func, + void *arg) +{ + const CERT_CONTEXT *current_context = NULL; + bool should_continue = true; + bool first = true; + bool reverse_order = false; + while(should_continue && + (current_context = CertEnumCertificatesInStore( + context->hCertStore, + current_context)) != NULL) { + /* Windows 11 22H2 OS Build 22621.674 or higher enumerates certificates in + leaf-to-root order while all previous versions of Windows enumerate + certificates in root-to-leaf order. Determine the order of enumeration + by comparing SECPKG_ATTR_REMOTE_CERT_CONTEXT's pbCertContext with the + first certificate's pbCertContext. */ + if(first && context->pbCertEncoded != current_context->pbCertEncoded) + reverse_order = true; + should_continue = func(current_context, reverse_order, arg); + first = false; + } + + if(current_context) + CertFreeCertificateContext(current_context); +} + +static bool +cert_counter_callback(const CERT_CONTEXT *ccert_context, bool reverse_order, + void *certs_count) +{ + (void)reverse_order; /* unused */ + if(valid_cert_encoding(ccert_context)) + (*(int *)certs_count)++; + return true; +} + +struct Adder_args +{ + struct Curl_easy *data; + CURLcode result; + int idx; + int certs_count; +}; + +static bool +add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, bool reverse_order, + void *raw_arg) +{ + struct Adder_args *args = (struct Adder_args*)raw_arg; + args->result = CURLE_OK; + if(valid_cert_encoding(ccert_context)) { + const char *beg = (const char *) ccert_context->pbCertEncoded; + const char *end = beg + ccert_context->cbCertEncoded; + int insert_index = reverse_order ? (args->certs_count - 1) - args->idx : + args->idx; + args->result = Curl_extract_certinfo(args->data, insert_index, + beg, end); + args->idx++; + } + return args->result == CURLE_OK; +} + +static CURLcode +schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + CURLcode result = CURLE_OK; + SECURITY_STATUS sspi_status = SEC_E_OK; + CERT_CONTEXT *ccert_context = NULL; +#ifdef HAS_ALPN + SecPkgContext_ApplicationProtocol alpn_result; +#endif + + DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + DEBUGASSERT(backend); + + DEBUGF(infof(data, + "schannel: SSL/TLS connection with %s port %d (step 3/3)", + connssl->peer.hostname, connssl->port)); + + if(!backend->cred) + return CURLE_SSL_CONNECT_ERROR; + + /* check if the required context attributes are met */ + if(backend->ret_flags != backend->req_flags) { + if(!(backend->ret_flags & ISC_RET_SEQUENCE_DETECT)) + failf(data, "schannel: failed to setup sequence detection"); + if(!(backend->ret_flags & ISC_RET_REPLAY_DETECT)) + failf(data, "schannel: failed to setup replay detection"); + if(!(backend->ret_flags & ISC_RET_CONFIDENTIALITY)) + failf(data, "schannel: failed to setup confidentiality"); + if(!(backend->ret_flags & ISC_RET_ALLOCATED_MEMORY)) + failf(data, "schannel: failed to setup memory allocation"); + if(!(backend->ret_flags & ISC_RET_STREAM)) + failf(data, "schannel: failed to setup stream orientation"); + return CURLE_SSL_CONNECT_ERROR; + } + +#ifdef HAS_ALPN + if(backend->use_alpn) { + sspi_status = + s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, + SECPKG_ATTR_APPLICATION_PROTOCOL, + &alpn_result); + + if(sspi_status != SEC_E_OK) { + failf(data, "schannel: failed to retrieve ALPN result"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(alpn_result.ProtoNegoStatus == + SecApplicationProtocolNegotiationStatus_Success) { + unsigned char prev_alpn = cf->conn->alpn; + + Curl_alpn_set_negotiated(cf, data, alpn_result.ProtocolId, + alpn_result.ProtocolIdSize); + if(backend->recv_renegotiating) { + if(prev_alpn != cf->conn->alpn && + prev_alpn != CURL_HTTP_VERSION_NONE) { + /* Renegotiation selected a different protocol now, we cannot + * deal with this */ + failf(data, "schannel: server selected an ALPN protocol too late"); + return CURLE_SSL_CONNECT_ERROR; + } + } + } + else { + if(!backend->recv_renegotiating) + Curl_alpn_set_negotiated(cf, data, NULL, 0); + } + } +#endif + + /* save the current session data for possible reuse */ + if(ssl_config->primary.sessionid) { + bool incache; + bool added = FALSE; + struct Curl_schannel_cred *old_cred = NULL; + + Curl_ssl_sessionid_lock(data); + incache = !(Curl_ssl_getsessionid(cf, data, (void **)&old_cred, NULL)); + if(incache) { + if(old_cred != backend->cred) { + DEBUGF(infof(data, + "schannel: old credential handle is stale, removing")); + /* we're not taking old_cred ownership here, no refcount++ is needed */ + Curl_ssl_delsessionid(data, (void *)old_cred); + incache = FALSE; + } + } + if(!incache) { + result = Curl_ssl_addsessionid(cf, data, backend->cred, + sizeof(struct Curl_schannel_cred), + &added); + if(result) { + Curl_ssl_sessionid_unlock(data); + failf(data, "schannel: failed to store credential handle"); + return result; + } + else if(added) { + /* this cred session is now also referenced by sessionid cache */ + backend->cred->refcount++; + DEBUGF(infof(data, + "schannel: stored credential handle in session cache")); + } + } + Curl_ssl_sessionid_unlock(data); + } + + if(data->set.ssl.certinfo) { + int certs_count = 0; + sspi_status = + s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &ccert_context); + + if((sspi_status != SEC_E_OK) || !ccert_context) { + failf(data, "schannel: failed to retrieve remote cert context"); + return CURLE_PEER_FAILED_VERIFICATION; + } + + traverse_cert_store(ccert_context, cert_counter_callback, &certs_count); + + result = Curl_ssl_init_certinfo(data, certs_count); + if(!result) { + struct Adder_args args; + args.data = data; + args.idx = 0; + args.certs_count = certs_count; + traverse_cert_store(ccert_context, add_cert_to_certinfo, &args); + result = args.result; + } + CertFreeCertificateContext(ccert_context); + if(result) + return result; + } + + connssl->connecting_state = ssl_connect_done; + + return CURLE_OK; +} + +static CURLcode +schannel_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool nonblocking, bool *done) +{ + CURLcode result; + struct ssl_connect_data *connssl = cf->ctx; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + timediff_t timeout_ms; + int what; + + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + if(ssl_connect_1 == connssl->connecting_state) { + /* check out how much more time we're allowed */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL/TLS connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + result = schannel_connect_step1(cf, data); + if(result) + return result; + } + + while(ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + + /* check out how much more time we're allowed */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL/TLS connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + if(connssl->connecting_state == ssl_connect_2_reading + || connssl->connecting_state == ssl_connect_2_writing) { + + curl_socket_t writefd = ssl_connect_2_writing == + connssl->connecting_state ? sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading == + connssl->connecting_state ? sockfd : CURL_SOCKET_BAD; + + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, + nonblocking ? 0 : timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL/TLS socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) { + *done = FALSE; + return CURLE_OK; + } + else { + /* timeout */ + failf(data, "SSL/TLS connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + /* Run transaction, and return to the caller if it failed or if + * this connection is part of a multi handle and this loop would + * execute again. This permits the owner of a multi handle to + * abort a connection attempt before step2 has completed while + * ensuring that a client using select() or epoll() will always + * have a valid fdset to wait on. + */ + result = schannel_connect_step2(cf, data); + if(result || (nonblocking && + (ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state))) + return result; + + } /* repeat step2 until all transactions are done. */ + + if(ssl_connect_3 == connssl->connecting_state) { + result = schannel_connect_step3(cf, data); + if(result) + return result; + } + + if(ssl_connect_done == connssl->connecting_state) { + connssl->state = ssl_connection_complete; + +#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS + /* 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 + + *done = TRUE; + } + else + *done = FALSE; + + /* reset our connection state machine */ + connssl->connecting_state = ssl_connect_1; + + return CURLE_OK; +} + +static ssize_t +schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + ssize_t written = -1; + size_t data_len = 0; + unsigned char *ptr = NULL; + struct ssl_connect_data *connssl = cf->ctx; + SecBuffer outbuf[4]; + SecBufferDesc outbuf_desc; + SECURITY_STATUS sspi_status = SEC_E_OK; + CURLcode result; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + + DEBUGASSERT(backend); + + /* check if the maximum stream sizes were queried */ + if(backend->stream_sizes.cbMaximumMessage == 0) { + sspi_status = s_pSecFn->QueryContextAttributes( + &backend->ctxt->ctxt_handle, + SECPKG_ATTR_STREAM_SIZES, + &backend->stream_sizes); + if(sspi_status != SEC_E_OK) { + *err = CURLE_SEND_ERROR; + return -1; + } + } + + /* 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 */ + data_len = backend->stream_sizes.cbHeader + len + + backend->stream_sizes.cbTrailer; + ptr = (unsigned char *) malloc(data_len); + if(!ptr) { + *err = CURLE_OUT_OF_MEMORY; + return -1; + } + + /* setup output buffers (header, data, trailer, empty) */ + InitSecBuffer(&outbuf[0], SECBUFFER_STREAM_HEADER, + ptr, backend->stream_sizes.cbHeader); + InitSecBuffer(&outbuf[1], SECBUFFER_DATA, + ptr + backend->stream_sizes.cbHeader, curlx_uztoul(len)); + InitSecBuffer(&outbuf[2], SECBUFFER_STREAM_TRAILER, + ptr + backend->stream_sizes.cbHeader + len, + backend->stream_sizes.cbTrailer); + InitSecBuffer(&outbuf[3], SECBUFFER_EMPTY, NULL, 0); + InitSecBufferDesc(&outbuf_desc, outbuf, 4); + + /* copy data into output buffer */ + memcpy(outbuf[1].pvBuffer, buf, len); + + /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */ + sspi_status = s_pSecFn->EncryptMessage(&backend->ctxt->ctxt_handle, 0, + &outbuf_desc, 0); + + /* check if the message was encrypted */ + if(sspi_status == SEC_E_OK) { + written = 0; + + /* send the encrypted message including header, data and trailer */ + len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer; + + /* + It's 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 + can't 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, will 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 > (size_t)written) { + ssize_t this_write = 0; + int what; + timediff_t timeout_ms = Curl_timeleft(data, NULL, FALSE); + if(timeout_ms < 0) { + /* we already got the timeout */ + failf(data, "schannel: timed out sending data " + "(bytes sent: %zd)", written); + *err = CURLE_OPERATION_TIMEDOUT; + written = -1; + break; + } + else if(!timeout_ms) + timeout_ms = TIMEDIFF_T_MAX; + what = SOCKET_WRITABLE(Curl_conn_cf_get_socket(cf, data), timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + *err = CURLE_SEND_ERROR; + written = -1; + break; + } + else if(0 == what) { + failf(data, "schannel: timed out sending data " + "(bytes sent: %zd)", written); + *err = CURLE_OPERATION_TIMEDOUT; + written = -1; + break; + } + /* socket is writable */ + + this_write = Curl_conn_cf_send(cf->next, data, + ptr + written, len - written, + &result); + if(result == CURLE_AGAIN) + continue; + else if(result != CURLE_OK) { + *err = result; + written = -1; + break; + } + + written += this_write; + } + } + else if(sspi_status == SEC_E_INSUFFICIENT_MEMORY) { + *err = CURLE_OUT_OF_MEMORY; + } + else{ + *err = CURLE_SEND_ERROR; + } + + Curl_safefree(ptr); + + if(len == (size_t)written) + /* Encrypted message including header, data and trailer entirely sent. + The return value is the number of unencrypted bytes that were sent. */ + written = outbuf[1].cbBuffer; + + return written; +} + +static ssize_t +schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + size_t size = 0; + ssize_t nread = -1; + struct ssl_connect_data *connssl = cf->ctx; + unsigned char *reallocated_buffer; + size_t reallocated_length; + bool done = FALSE; + SecBuffer inbuf[4]; + SecBufferDesc inbuf_desc; + SECURITY_STATUS sspi_status = SEC_E_OK; + /* we want the length of the encrypted buffer to be at least large enough + that it can hold all the bytes requested and some TLS record overhead. */ + size_t min_encdata_length = len + CURL_SCHANNEL_BUFFER_FREE_SIZE; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + + DEBUGASSERT(backend); + + /**************************************************************************** + * Don't return or set backend->recv_unrecoverable_err unless in the cleanup. + * The pattern for return error is set *err, optional infof, goto cleanup. + * + * Our priority is to always return as much decrypted data to the caller as + * possible, even if an error occurs. The state of the decrypted buffer must + * always be valid. Transfer of decrypted data to the caller's buffer is + * handled in the cleanup. + */ + + DEBUGF(infof(data, "schannel: client wants to read %zu bytes", len)); + *err = CURLE_OK; + + if(len && len <= backend->decdata_offset) { + infof(data, "schannel: enough decrypted data is already available"); + goto cleanup; + } + else if(backend->recv_unrecoverable_err) { + *err = backend->recv_unrecoverable_err; + infof(data, "schannel: an unrecoverable error occurred in a prior call"); + goto cleanup; + } + else if(backend->recv_sspi_close_notify) { + /* once a server has indicated shutdown there is no more encrypted data */ + infof(data, "schannel: server indicated shutdown in a prior call"); + goto cleanup; + } + + /* It's debatable what to return when !len. Regardless we can't 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. + */ + else if(len && !backend->recv_connection_closed) { + /* increase enc buffer in order to fit the requested amount of data */ + size = backend->encdata_length - backend->encdata_offset; + if(size < CURL_SCHANNEL_BUFFER_FREE_SIZE || + backend->encdata_length < min_encdata_length) { + reallocated_length = backend->encdata_offset + + CURL_SCHANNEL_BUFFER_FREE_SIZE; + if(reallocated_length < min_encdata_length) { + reallocated_length = min_encdata_length; + } + reallocated_buffer = realloc(backend->encdata_buffer, + reallocated_length); + if(!reallocated_buffer) { + *err = CURLE_OUT_OF_MEMORY; + failf(data, "schannel: unable to re-allocate memory"); + goto cleanup; + } + + backend->encdata_buffer = reallocated_buffer; + backend->encdata_length = reallocated_length; + size = backend->encdata_length - backend->encdata_offset; + DEBUGF(infof(data, "schannel: encdata_buffer resized %zu", + backend->encdata_length)); + } + + DEBUGF(infof(data, + "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); + + /* read encrypted data from socket */ + nread = Curl_conn_cf_recv(cf->next, data, + (char *)(backend->encdata_buffer + + backend->encdata_offset), + size, err); + if(*err) { + nread = -1; + if(*err == CURLE_AGAIN) + DEBUGF(infof(data, + "schannel: recv returned CURLE_AGAIN")); + else if(*err == CURLE_RECV_ERROR) + infof(data, "schannel: recv returned CURLE_RECV_ERROR"); + else + infof(data, "schannel: recv returned error %d", *err); + } + else if(nread == 0) { + backend->recv_connection_closed = true; + DEBUGF(infof(data, "schannel: server closed the connection")); + } + else if(nread > 0) { + backend->encdata_offset += (size_t)nread; + backend->encdata_is_incomplete = false; + DEBUGF(infof(data, "schannel: encrypted data got %zd", nread)); + } + } + + DEBUGF(infof(data, + "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); + + /* decrypt loop */ + while(backend->encdata_offset > 0 && sspi_status == SEC_E_OK && + (!len || backend->decdata_offset < len || + backend->recv_connection_closed)) { + /* prepare data buffer for DecryptMessage call */ + InitSecBuffer(&inbuf[0], SECBUFFER_DATA, backend->encdata_buffer, + curlx_uztoul(backend->encdata_offset)); + + /* we need 3 more empty input buffers for possible output */ + InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0); + InitSecBuffer(&inbuf[2], SECBUFFER_EMPTY, NULL, 0); + InitSecBuffer(&inbuf[3], SECBUFFER_EMPTY, NULL, 0); + InitSecBufferDesc(&inbuf_desc, inbuf, 4); + + /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx + */ + sspi_status = s_pSecFn->DecryptMessage(&backend->ctxt->ctxt_handle, + &inbuf_desc, 0, NULL); + + /* check if everything went fine (server may want to renegotiate + or shutdown the connection context) */ + if(sspi_status == SEC_E_OK || sspi_status == SEC_I_RENEGOTIATE || + sspi_status == SEC_I_CONTEXT_EXPIRED) { + /* check for successfully decrypted data, even before actual + renegotiation or shutdown of the connection context */ + if(inbuf[1].BufferType == SECBUFFER_DATA) { + DEBUGF(infof(data, "schannel: decrypted data length: %lu", + inbuf[1].cbBuffer)); + + /* increase buffer in order to fit the received amount of data */ + size = inbuf[1].cbBuffer > CURL_SCHANNEL_BUFFER_FREE_SIZE ? + inbuf[1].cbBuffer : CURL_SCHANNEL_BUFFER_FREE_SIZE; + if(backend->decdata_length - backend->decdata_offset < size || + backend->decdata_length < len) { + /* increase internal decrypted data buffer */ + reallocated_length = backend->decdata_offset + size; + /* make sure that the requested amount of data fits */ + if(reallocated_length < len) { + reallocated_length = len; + } + reallocated_buffer = realloc(backend->decdata_buffer, + reallocated_length); + if(!reallocated_buffer) { + *err = CURLE_OUT_OF_MEMORY; + failf(data, "schannel: unable to re-allocate memory"); + goto cleanup; + } + backend->decdata_buffer = reallocated_buffer; + backend->decdata_length = reallocated_length; + } + + /* copy decrypted data to internal buffer */ + size = inbuf[1].cbBuffer; + if(size) { + memcpy(backend->decdata_buffer + backend->decdata_offset, + inbuf[1].pvBuffer, size); + backend->decdata_offset += size; + } + + DEBUGF(infof(data, "schannel: decrypted data added: %zu", size)); + DEBUGF(infof(data, + "schannel: decrypted cached: offset %zu length %zu", + backend->decdata_offset, backend->decdata_length)); + } + + /* check for remaining encrypted data */ + if(inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) { + DEBUGF(infof(data, "schannel: encrypted data length: %lu", + inbuf[3].cbBuffer)); + + /* check if the remaining data is less than the total amount + * and therefore begins after the already processed data + */ + if(backend->encdata_offset > inbuf[3].cbBuffer) { + /* move remaining encrypted data forward to the beginning of + buffer */ + memmove(backend->encdata_buffer, + (backend->encdata_buffer + backend->encdata_offset) - + inbuf[3].cbBuffer, inbuf[3].cbBuffer); + backend->encdata_offset = inbuf[3].cbBuffer; + } + + DEBUGF(infof(data, + "schannel: encrypted cached: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); + } + else { + /* reset encrypted buffer offset, because there is no data remaining */ + backend->encdata_offset = 0; + } + + /* check if server wants to renegotiate the connection context */ + if(sspi_status == SEC_I_RENEGOTIATE) { + infof(data, "schannel: remote party requests renegotiation"); + if(*err && *err != CURLE_AGAIN) { + infof(data, "schannel: can't renegotiate, an error is pending"); + goto cleanup; + } + + /* begin renegotiation */ + infof(data, "schannel: renegotiating SSL/TLS connection"); + connssl->state = ssl_connection_negotiating; + connssl->connecting_state = ssl_connect_2_writing; + backend->recv_renegotiating = true; + *err = schannel_connect_common(cf, data, FALSE, &done); + backend->recv_renegotiating = false; + if(*err) { + infof(data, "schannel: renegotiation failed"); + goto cleanup; + } + /* now retry receiving data */ + sspi_status = SEC_E_OK; + infof(data, "schannel: SSL/TLS connection renegotiated"); + continue; + } + /* check if the server closed the connection */ + else if(sspi_status == SEC_I_CONTEXT_EXPIRED) { + /* In Windows 2000 SEC_I_CONTEXT_EXPIRED (close_notify) is not + returned so we have to work around that in cleanup. */ + backend->recv_sspi_close_notify = true; + if(!backend->recv_connection_closed) { + backend->recv_connection_closed = true; + infof(data, "schannel: server closed the connection"); + } + goto cleanup; + } + } + else if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) { + backend->encdata_is_incomplete = true; + if(!*err) + *err = CURLE_AGAIN; + infof(data, "schannel: failed to decrypt data, need more data"); + goto cleanup; + } + else { +#ifndef CURL_DISABLE_VERBOSE_STRINGS + char buffer[STRERROR_LEN]; +#endif + *err = CURLE_RECV_ERROR; + infof(data, "schannel: failed to read data from server: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + goto cleanup; + } + } + + DEBUGF(infof(data, + "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); + + DEBUGF(infof(data, + "schannel: decrypted data buffer: offset %zu length %zu", + backend->decdata_offset, backend->decdata_length)); + +cleanup: + /* Warning- there is no guarantee the encdata state is valid at this point */ + DEBUGF(infof(data, "schannel: schannel_recv cleanup")); + + /* Error if the connection has closed without a close_notify. + + The behavior here is a matter of debate. We don't want to be vulnerable + to a truncation attack however there's some browser precedent for + ignoring the close_notify for compatibility reasons. + + Additionally, Windows 2000 (v5.0) is a special case since it seems it + doesn't return close_notify. In that case if the connection was closed we + assume it was graceful (close_notify) since there doesn't seem to be a + way to tell. + */ + if(len && !backend->decdata_offset && backend->recv_connection_closed && + !backend->recv_sspi_close_notify) { + bool isWin2k = curlx_verify_windows_version(5, 0, 0, PLATFORM_WINNT, + VERSION_EQUAL); + + if(isWin2k && sspi_status == SEC_E_OK) + backend->recv_sspi_close_notify = true; + else { + *err = CURLE_RECV_ERROR; + infof(data, "schannel: server closed abruptly (missing close_notify)"); + } + } + + /* Any error other than CURLE_AGAIN is an unrecoverable error. */ + if(*err && *err != CURLE_AGAIN) + backend->recv_unrecoverable_err = *err; + + size = len < backend->decdata_offset ? len : backend->decdata_offset; + if(size) { + memcpy(buf, backend->decdata_buffer, size); + memmove(backend->decdata_buffer, backend->decdata_buffer + size, + backend->decdata_offset - size); + backend->decdata_offset -= size; + DEBUGF(infof(data, "schannel: decrypted data returned %zu", size)); + DEBUGF(infof(data, + "schannel: decrypted data buffer: offset %zu length %zu", + backend->decdata_offset, backend->decdata_length)); + *err = CURLE_OK; + return (ssize_t)size; + } + + if(!*err && !backend->recv_connection_closed) + *err = CURLE_AGAIN; + + /* It's 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. + */ + if(!len) + *err = CURLE_OK; + + return *err ? -1 : 0; +} + +static CURLcode schannel_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + return schannel_connect_common(cf, data, TRUE, done); +} + +static CURLcode schannel_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result; + bool done = FALSE; + + result = schannel_connect_common(cf, data, FALSE, &done); + if(result) + return result; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +static bool schannel_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + const struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + + (void)data; + DEBUGASSERT(backend); + + if(backend->ctxt) /* SSL/TLS is in use */ + return (backend->decdata_offset > 0 || + (backend->encdata_offset > 0 && !backend->encdata_is_incomplete)); + else + return FALSE; +} + +static void schannel_session_free(void *ptr) +{ + /* this is expected to be called under sessionid lock */ + struct Curl_schannel_cred *cred = ptr; + + if(cred) { + cred->refcount--; + if(cred->refcount == 0) { + s_pSecFn->FreeCredentialsHandle(&cred->cred_handle); + curlx_unicodefree(cred->sni_hostname); +#ifdef HAS_CLIENT_CERT_PATH + if(cred->client_cert_store) { + CertCloseStore(cred->client_cert_store, 0); + cred->client_cert_store = NULL; + } +#endif + Curl_safefree(cred); + } + } +} + +/* shut down the SSL connection and clean up related memory. + this function can be called multiple times on the same connection including + if the SSL connection failed (eg connection made but failed handshake). */ +static int schannel_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + /* See https://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx + * Shutting Down an Schannel Connection + */ + struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + + DEBUGASSERT(data); + DEBUGASSERT(backend); + + if(backend->ctxt) { + infof(data, "schannel: shutting down SSL/TLS connection with %s port %d", + connssl->peer.hostname, connssl->port); + } + + if(backend->cred && backend->ctxt) { + SecBufferDesc BuffDesc; + SecBuffer Buffer; + SECURITY_STATUS sspi_status; + SecBuffer outbuf; + SecBufferDesc outbuf_desc; + CURLcode result; + DWORD dwshut = SCHANNEL_SHUTDOWN; + + InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut)); + InitSecBufferDesc(&BuffDesc, &Buffer, 1); + + sspi_status = s_pSecFn->ApplyControlToken(&backend->ctxt->ctxt_handle, + &BuffDesc); + + if(sspi_status != SEC_E_OK) { + char buffer[STRERROR_LEN]; + failf(data, "schannel: ApplyControlToken failure: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + } + + /* setup output buffer */ + InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); + InitSecBufferDesc(&outbuf_desc, &outbuf, 1); + + sspi_status = s_pSecFn->InitializeSecurityContext( + &backend->cred->cred_handle, + &backend->ctxt->ctxt_handle, + backend->cred->sni_hostname, + backend->req_flags, + 0, + 0, + NULL, + 0, + &backend->ctxt->ctxt_handle, + &outbuf_desc, + &backend->ret_flags, + &backend->ctxt->time_stamp); + + if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) { + /* send close message which is in output buffer */ + ssize_t written = Curl_conn_cf_send(cf->next, data, + outbuf.pvBuffer, outbuf.cbBuffer, + &result); + s_pSecFn->FreeContextBuffer(outbuf.pvBuffer); + if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) { + infof(data, "schannel: failed to send close msg: %s" + " (bytes written: %zd)", curl_easy_strerror(result), written); + } + } + } + + /* free SSPI Schannel API security context handle */ + if(backend->ctxt) { + DEBUGF(infof(data, "schannel: clear security context handle")); + s_pSecFn->DeleteSecurityContext(&backend->ctxt->ctxt_handle); + Curl_safefree(backend->ctxt); + } + + /* free SSPI Schannel API credential handle */ + if(backend->cred) { + Curl_ssl_sessionid_lock(data); + schannel_session_free(backend->cred); + Curl_ssl_sessionid_unlock(data); + backend->cred = NULL; + } + + /* free internal buffer for received encrypted data */ + if(backend->encdata_buffer) { + Curl_safefree(backend->encdata_buffer); + backend->encdata_length = 0; + backend->encdata_offset = 0; + backend->encdata_is_incomplete = false; + } + + /* free internal buffer for received decrypted data */ + if(backend->decdata_buffer) { + Curl_safefree(backend->decdata_buffer); + backend->decdata_length = 0; + backend->decdata_offset = 0; + } + + return CURLE_OK; +} + +static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + schannel_shutdown(cf, data); +} + +static int schannel_init(void) +{ + return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0); +} + +static void schannel_cleanup(void) +{ + Curl_sspi_global_cleanup(); +} + +static size_t schannel_version(char *buffer, size_t size) +{ + size = msnprintf(buffer, size, "Schannel"); + + return size; +} + +static CURLcode schannel_random(struct Curl_easy *data UNUSED_PARAM, + unsigned char *entropy, size_t length) +{ + (void)data; + + return Curl_win32_random(entropy, length); +} + +static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *pinnedpubkey) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + CERT_CONTEXT *pCertContextServer = NULL; + + /* Result is returned to caller */ + CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; + + DEBUGASSERT(backend); + + /* if a path wasn't specified, don't pin */ + if(!pinnedpubkey) + return CURLE_OK; + + do { + SECURITY_STATUS sspi_status; + const char *x509_der; + DWORD x509_der_len; + struct Curl_X509certificate x509_parsed; + struct Curl_asn1Element *pubkey; + + sspi_status = + s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &pCertContextServer); + + if((sspi_status != SEC_E_OK) || !pCertContextServer) { + char buffer[STRERROR_LEN]; + failf(data, "schannel: Failed to read remote certificate context: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + break; /* failed */ + } + + + if(!(((pCertContextServer->dwCertEncodingType & X509_ASN_ENCODING) != 0) && + (pCertContextServer->cbCertEncoded > 0))) + break; + + x509_der = (const char *)pCertContextServer->pbCertEncoded; + x509_der_len = pCertContextServer->cbCertEncoded; + memset(&x509_parsed, 0, sizeof(x509_parsed)); + if(Curl_parseX509(&x509_parsed, x509_der, x509_der + x509_der_len)) + break; + + pubkey = &x509_parsed.subjectPublicKeyInfo; + if(!pubkey->header || pubkey->end <= pubkey->header) { + failf(data, "SSL: failed retrieving public key from server certificate"); + break; + } + + result = Curl_pin_peer_pubkey(data, + pinnedpubkey, + (const unsigned char *)pubkey->header, + (size_t)(pubkey->end - pubkey->header)); + if(result) { + failf(data, "SSL: public key does not match pinned public key"); + } + } while(0); + + if(pCertContextServer) + CertFreeCertificateContext(pCertContextServer); + + return result; +} + +static void schannel_checksum(const unsigned char *input, + size_t inputlen, + unsigned char *checksum, + size_t checksumlen, + DWORD provType, + const unsigned int algId) +{ + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + DWORD cbHashSize = 0; + DWORD dwHashSizeLen = (DWORD)sizeof(cbHashSize); + DWORD dwChecksumLen = (DWORD)checksumlen; + + /* since this can fail in multiple ways, zero memory first so we never + * return old data + */ + memset(checksum, 0, checksumlen); + + if(!CryptAcquireContext(&hProv, NULL, NULL, provType, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + return; /* failed */ + + do { + if(!CryptCreateHash(hProv, algId, 0, 0, &hHash)) + break; /* failed */ + + if(!CryptHashData(hHash, input, (DWORD)inputlen, 0)) + break; /* failed */ + + /* get hash size */ + if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&cbHashSize, + &dwHashSizeLen, 0)) + break; /* failed */ + + /* check hash size */ + if(checksumlen < cbHashSize) + break; /* failed */ + + if(CryptGetHashParam(hHash, HP_HASHVAL, checksum, &dwChecksumLen, 0)) + break; /* failed */ + } while(0); + + if(hHash) + CryptDestroyHash(hHash); + + if(hProv) + CryptReleaseContext(hProv, 0); +} + +static CURLcode schannel_sha256sum(const unsigned char *input, + size_t inputlen, + unsigned char *sha256sum, + size_t sha256len) +{ + schannel_checksum(input, inputlen, sha256sum, sha256len, + PROV_RSA_AES, CALG_SHA_256); + return CURLE_OK; +} + +static void *schannel_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) +{ + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + (void)info; + DEBUGASSERT(backend); + return &backend->ctxt->ctxt_handle; +} + +HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + struct schannel_multi_ssl_backend_data *mbackend; + const struct ssl_general_config *cfg = &data->set.general_ssl; + timediff_t timeout_ms; + timediff_t elapsed_ms; + struct curltime now; + unsigned char info_blob_digest[CURL_SHA256_DIGEST_LENGTH]; + + DEBUGASSERT(multi); + + if(!multi || !multi->ssl_backend_data) { + return NULL; + } + + mbackend = (struct schannel_multi_ssl_backend_data *)multi->ssl_backend_data; + if(!mbackend->cert_store) { + return NULL; + } + + /* zero ca_cache_timeout completely disables caching */ + if(!cfg->ca_cache_timeout) { + return NULL; + } + + /* check for cache timeout by using the cached_x509_store_expired timediff + calculation pattern from openssl.c. + negative timeout means retain forever. */ + timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + if(timeout_ms >= 0) { + now = Curl_now(); + elapsed_ms = Curl_timediff(now, mbackend->time); + if(elapsed_ms >= timeout_ms) { + return NULL; + } + } + + if(ca_info_blob) { + if(!mbackend->CAinfo_blob_digest) { + return NULL; + } + if(mbackend->CAinfo_blob_size != ca_info_blob->len) { + return NULL; + } + schannel_sha256sum((const unsigned char *)ca_info_blob->data, + ca_info_blob->len, + info_blob_digest, + CURL_SHA256_DIGEST_LENGTH); + if(memcmp(mbackend->CAinfo_blob_digest, + info_blob_digest, + CURL_SHA256_DIGEST_LENGTH)) { + return NULL; + } + } + else { + if(!conn_config->CAfile || !mbackend->CAfile || + strcmp(mbackend->CAfile, conn_config->CAfile)) { + return NULL; + } + } + + return mbackend->cert_store; +} + +bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data, + HCERTSTORE cert_store) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + struct schannel_multi_ssl_backend_data *mbackend; + unsigned char *CAinfo_blob_digest = NULL; + size_t CAinfo_blob_size = 0; + char *CAfile = NULL; + + DEBUGASSERT(multi); + + if(!multi) { + return false; + } + + if(!multi->ssl_backend_data) { + multi->ssl_backend_data = + calloc(1, sizeof(struct schannel_multi_ssl_backend_data)); + if(!multi->ssl_backend_data) { + return false; + } + } + + mbackend = (struct schannel_multi_ssl_backend_data *)multi->ssl_backend_data; + + + if(ca_info_blob) { + CAinfo_blob_digest = malloc(CURL_SHA256_DIGEST_LENGTH); + if(!CAinfo_blob_digest) { + return false; + } + schannel_sha256sum((const unsigned char *)ca_info_blob->data, + ca_info_blob->len, + CAinfo_blob_digest, + CURL_SHA256_DIGEST_LENGTH); + CAinfo_blob_size = ca_info_blob->len; + } + else { + if(conn_config->CAfile) { + CAfile = strdup(conn_config->CAfile); + if(!CAfile) { + return false; + } + } + } + + /* free old cache data */ + if(mbackend->cert_store) { + CertCloseStore(mbackend->cert_store, 0); + } + free(mbackend->CAinfo_blob_digest); + free(mbackend->CAfile); + + mbackend->time = Curl_now(); + mbackend->cert_store = cert_store; + mbackend->CAinfo_blob_digest = CAinfo_blob_digest; + mbackend->CAinfo_blob_size = CAinfo_blob_size; + mbackend->CAfile = CAfile; + return true; +} + +static void schannel_free_multi_ssl_backend_data( + struct multi_ssl_backend_data *msbd) +{ + struct schannel_multi_ssl_backend_data *mbackend = + (struct schannel_multi_ssl_backend_data*)msbd; + if(mbackend->cert_store) { + CertCloseStore(mbackend->cert_store, 0); + } + free(mbackend->CAinfo_blob_digest); + free(mbackend->CAfile); + free(mbackend); +} + +const struct Curl_ssl Curl_ssl_schannel = { + { CURLSSLBACKEND_SCHANNEL, "schannel" }, /* info */ + + SSLSUPP_CERTINFO | +#ifdef HAS_MANUAL_VERIFY_API + SSLSUPP_CAINFO_BLOB | +#endif + SSLSUPP_PINNEDPUBKEY | + SSLSUPP_TLS13_CIPHERSUITES | + SSLSUPP_HTTPS_PROXY, + + sizeof(struct schannel_ssl_backend_data), + + schannel_init, /* init */ + schannel_cleanup, /* cleanup */ + schannel_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + schannel_shutdown, /* shutdown */ + schannel_data_pending, /* data_pending */ + schannel_random, /* random */ + Curl_none_cert_status_request, /* cert_status_request */ + schannel_connect, /* connect */ + schannel_connect_nonblocking, /* connect_nonblocking */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ + schannel_get_internals, /* get_internals */ + schannel_close, /* close_one */ + Curl_none_close_all, /* close_all */ + schannel_session_free, /* session_free */ + Curl_none_set_engine, /* set_engine */ + Curl_none_set_engine_default, /* set_engine_default */ + Curl_none_engines_list, /* engines_list */ + Curl_none_false_start, /* false_start */ + schannel_sha256sum, /* sha256sum */ + NULL, /* associate_connection */ + NULL, /* disassociate_connection */ + schannel_free_multi_ssl_backend_data, /* free_multi_ssl_backend_data */ + schannel_recv, /* recv decrypted data */ + schannel_send, /* send data to encrypt */ +}; + +#endif /* USE_SCHANNEL */ diff --git a/Utilities/cmcurl/lib/vtls/schannel.h b/Utilities/cmcurl/lib/vtls/schannel.h new file mode 100644 index 0000000..b26334b --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/schannel.h @@ -0,0 +1,86 @@ +#ifndef HEADER_CURL_SCHANNEL_H +#define HEADER_CURL_SCHANNEL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Marc Hoersken, <info@marc-hoersken.de>, et al. + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_SCHANNEL + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4201) +#endif +#include <subauth.h> +#ifdef _MSC_VER +#pragma warning(pop) +#endif +/* Wincrypt must be included before anything that could include OpenSSL. */ +#if defined(USE_WIN32_CRYPTO) +#include <wincrypt.h> +/* Undefine wincrypt conflicting symbols for BoringSSL. */ +#undef X509_NAME +#undef X509_EXTENSIONS +#undef PKCS7_ISSUER_AND_SERIAL +#undef PKCS7_SIGNER_INFO +#undef OCSP_REQUEST +#undef OCSP_RESPONSE +#endif + +#include <schnlsp.h> +#include <schannel.h> +#include "curl_sspi.h" + +#include "cfilters.h" +#include "urldata.h" + +/* <wincrypt.h> has been included via the above <schnlsp.h>. + * Or in case of ldap.c, it was included via <winldap.h>. + * And since <wincrypt.h> has this: + * #define X509_NAME ((LPCSTR) 7) + * + * And in BoringSSL's <openssl/base.h> there is: + * typedef struct X509_name_st X509_NAME; + * etc. + * + * this will cause all kinds of C-preprocessing paste errors in + * BoringSSL's <openssl/x509.h>: So just undefine those defines here + * (and only here). + */ +#if defined(OPENSSL_IS_BORINGSSL) +# undef X509_NAME +# undef X509_CERT_PAIR +# undef X509_EXTENSIONS +#endif + +extern const struct Curl_ssl Curl_ssl_schannel; + +CURLcode Curl_verify_host(struct Curl_cfilter *cf, + struct Curl_easy *data); + +CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, + struct Curl_easy *data); + +#endif /* USE_SCHANNEL */ +#endif /* HEADER_CURL_SCHANNEL_H */ diff --git a/Utilities/cmcurl/lib/vtls/schannel_int.h b/Utilities/cmcurl/lib/vtls/schannel_int.h new file mode 100644 index 0000000..fe7450d --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/schannel_int.h @@ -0,0 +1,170 @@ +#ifndef HEADER_CURL_SCHANNEL_INT_H +#define HEADER_CURL_SCHANNEL_INT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Marc Hoersken, <info@marc-hoersken.de>, et al. + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_SCHANNEL + +#if defined(__MINGW32__) || defined(CERT_CHAIN_REVOCATION_CHECK_CHAIN) +#define HAS_MANUAL_VERIFY_API +#endif + +#if defined(CryptStringToBinary) && defined(CRYPT_STRING_HEX) \ + && !defined(DISABLE_SCHANNEL_CLIENT_CERT) +#define HAS_CLIENT_CERT_PATH +#endif + +#ifndef CRYPT_DECODE_NOCOPY_FLAG +#define CRYPT_DECODE_NOCOPY_FLAG 0x1 +#endif + +#ifndef CRYPT_DECODE_ALLOC_FLAG +#define CRYPT_DECODE_ALLOC_FLAG 0x8000 +#endif + +#ifndef CERT_ALT_NAME_DNS_NAME +#define CERT_ALT_NAME_DNS_NAME 3 +#endif + +#ifndef CERT_ALT_NAME_IP_ADDRESS +#define CERT_ALT_NAME_IP_ADDRESS 8 +#endif + + +#ifndef SCH_CREDENTIALS_VERSION + +#define SCH_CREDENTIALS_VERSION 0x00000005 + +typedef enum _eTlsAlgorithmUsage +{ + TlsParametersCngAlgUsageKeyExchange, + TlsParametersCngAlgUsageSignature, + TlsParametersCngAlgUsageCipher, + TlsParametersCngAlgUsageDigest, + TlsParametersCngAlgUsageCertSig +} eTlsAlgorithmUsage; + +typedef struct _CRYPTO_SETTINGS +{ + eTlsAlgorithmUsage eAlgorithmUsage; + UNICODE_STRING strCngAlgId; + DWORD cChainingModes; + PUNICODE_STRING rgstrChainingModes; + DWORD dwMinBitLength; + DWORD dwMaxBitLength; +} CRYPTO_SETTINGS, * PCRYPTO_SETTINGS; + +typedef struct _TLS_PARAMETERS +{ + DWORD cAlpnIds; + PUNICODE_STRING rgstrAlpnIds; + DWORD grbitDisabledProtocols; + DWORD cDisabledCrypto; + PCRYPTO_SETTINGS pDisabledCrypto; + DWORD dwFlags; +} TLS_PARAMETERS, * PTLS_PARAMETERS; + +typedef struct _SCH_CREDENTIALS +{ + DWORD dwVersion; + DWORD dwCredFormat; + DWORD cCreds; + PCCERT_CONTEXT* paCred; + HCERTSTORE hRootStore; + + DWORD cMappers; + struct _HMAPPER **aphMappers; + + DWORD dwSessionLifespan; + DWORD dwFlags; + DWORD cTlsParameters; + PTLS_PARAMETERS pTlsParameters; +} SCH_CREDENTIALS, * PSCH_CREDENTIALS; + +#define SCH_CRED_MAX_SUPPORTED_PARAMETERS 16 +#define SCH_CRED_MAX_SUPPORTED_ALPN_IDS 16 +#define SCH_CRED_MAX_SUPPORTED_CRYPTO_SETTINGS 16 +#define SCH_CRED_MAX_SUPPORTED_CHAINING_MODES 16 + +#endif /* SCH_CREDENTIALS_VERSION */ + +struct Curl_schannel_cred { + CredHandle cred_handle; + TimeStamp time_stamp; + TCHAR *sni_hostname; +#ifdef HAS_CLIENT_CERT_PATH + HCERTSTORE client_cert_store; +#endif + int refcount; +}; + +struct Curl_schannel_ctxt { + CtxtHandle ctxt_handle; + TimeStamp time_stamp; +}; + +struct schannel_ssl_backend_data { + struct Curl_schannel_cred *cred; + struct Curl_schannel_ctxt *ctxt; + SecPkgContext_StreamSizes stream_sizes; + size_t encdata_length, decdata_length; + size_t encdata_offset, decdata_offset; + unsigned char *encdata_buffer, *decdata_buffer; + /* encdata_is_incomplete: if encdata contains only a partial record that + can't be decrypted without another recv() (that is, status is + SEC_E_INCOMPLETE_MESSAGE) then set this true. after an recv() adds + more bytes into encdata then set this back to false. */ + bool encdata_is_incomplete; + unsigned long req_flags, ret_flags; + CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */ + bool recv_sspi_close_notify; /* true if connection closed by close_notify */ + bool recv_connection_closed; /* true if connection closed, regardless how */ + bool recv_renegotiating; /* true if recv is doing renegotiation */ + bool use_alpn; /* true if ALPN is used for this connection */ +#ifdef HAS_MANUAL_VERIFY_API + bool use_manual_cred_validation; /* true if manual cred validation is used */ +#endif +}; + +struct schannel_multi_ssl_backend_data { + unsigned char *CAinfo_blob_digest; /* CA info blob digest */ + size_t CAinfo_blob_size; /* CA info blob size */ + char *CAfile; /* CAfile path used to generate + certificate store */ + HCERTSTORE cert_store; /* cached certificate store or + NULL if none */ + struct curltime time; /* when the cached store was created */ +}; + +HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data); + +bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data, + HCERTSTORE cert_store); + +#endif /* USE_SCHANNEL */ +#endif /* HEADER_CURL_SCHANNEL_INT_H */ diff --git a/Utilities/cmcurl/lib/vtls/schannel_verify.c b/Utilities/cmcurl/lib/vtls/schannel_verify.c new file mode 100644 index 0000000..e7c8bc6 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/schannel_verify.c @@ -0,0 +1,787 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Marc Hoersken, <info@marc-hoersken.de> + * Copyright (C) Mark Salisbury, <mark.salisbury@hp.com> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Source file for Schannel-specific certificate verification. This code should + * only be invoked by code in schannel.c. + */ + +#include "curl_setup.h" + +#ifdef USE_SCHANNEL +#ifndef USE_WINDOWS_SSPI +# error "Can't compile SCHANNEL support without SSPI." +#endif + +#include "schannel.h" +#include "schannel_int.h" + +#include "vtls.h" +#include "vtls_int.h" +#include "sendf.h" +#include "strerror.h" +#include "curl_multibyte.h" +#include "curl_printf.h" +#include "hostcheck.h" +#include "version_win32.h" + +/* The last #include file should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +#define BACKEND ((struct schannel_ssl_backend_data *)connssl->backend) + + +#ifdef HAS_MANUAL_VERIFY_API + +#define MAX_CAFILE_SIZE 1048576 /* 1 MiB */ +#define BEGIN_CERT "-----BEGIN CERTIFICATE-----" +#define END_CERT "\n-----END CERTIFICATE-----" + +struct cert_chain_engine_config_win7 { + DWORD cbSize; + HCERTSTORE hRestrictedRoot; + HCERTSTORE hRestrictedTrust; + HCERTSTORE hRestrictedOther; + DWORD cAdditionalStore; + HCERTSTORE *rghAdditionalStore; + DWORD dwFlags; + DWORD dwUrlRetrievalTimeout; + DWORD MaximumCachedCertificates; + DWORD CycleDetectionModulus; + HCERTSTORE hExclusiveRoot; + HCERTSTORE hExclusiveTrustedPeople; +}; + +static int is_cr_or_lf(char c) +{ + return c == '\r' || c == '\n'; +} + +/* Search the substring needle,needlelen into string haystack,haystacklen + * Strings don't need to be terminated by a '\0'. + * Similar of OSX/Linux memmem (not available on Visual Studio). + * Return position of beginning of first occurrence or NULL if not found + */ +static const char *c_memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen) +{ + const char *p; + char first; + const char *str_limit = (const char *)haystack + haystacklen; + if(!needlelen || needlelen > haystacklen) + return NULL; + first = *(const char *)needle; + for(p = (const char *)haystack; p <= (str_limit - needlelen); p++) + if(((*p) == first) && (memcmp(p, needle, needlelen) == 0)) + return p; + + return NULL; +} + +static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, + const char *ca_buffer, + size_t ca_buffer_size, + 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); + CURLcode result = CURLE_OK; + int num_certs = 0; + bool more_certs = 1; + const char *current_ca_file_ptr = ca_buffer; + const char *ca_buffer_limit = ca_buffer + ca_buffer_size; + + while(more_certs && (current_ca_file_ptr<ca_buffer_limit)) { + const char *begin_cert_ptr = c_memmem(current_ca_file_ptr, + ca_buffer_limit-current_ca_file_ptr, + BEGIN_CERT, + begin_cert_len); + if(!begin_cert_ptr || !is_cr_or_lf(begin_cert_ptr[begin_cert_len])) { + more_certs = 0; + } + else { + const char *end_cert_ptr = c_memmem(begin_cert_ptr, + ca_buffer_limit-begin_cert_ptr, + END_CERT, + end_cert_len); + if(!end_cert_ptr) { + failf(data, + "schannel: CA file '%s' is not correctly formatted", + ca_file_text); + result = CURLE_SSL_CACERT_BADFILE; + more_certs = 0; + } + else { + CERT_BLOB cert_blob; + CERT_CONTEXT *cert_context = NULL; + BOOL add_cert_result = FALSE; + DWORD actual_content_type = 0; + DWORD cert_size = (DWORD) + ((end_cert_ptr + end_cert_len) - begin_cert_ptr); + + cert_blob.pbData = (BYTE *)begin_cert_ptr; + cert_blob.cbData = cert_size; + if(!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, + &cert_blob, + CERT_QUERY_CONTENT_FLAG_CERT, + CERT_QUERY_FORMAT_FLAG_ALL, + 0, + NULL, + &actual_content_type, + NULL, + NULL, + NULL, + (const void **)&cert_context)) { + char buffer[STRERROR_LEN]; + failf(data, + "schannel: failed to extract certificate from CA file " + "'%s': %s", + ca_file_text, + Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + result = CURLE_SSL_CACERT_BADFILE; + more_certs = 0; + } + else { + current_ca_file_ptr = begin_cert_ptr + cert_size; + + /* Sanity check that the cert_context object is the right type */ + if(CERT_QUERY_CONTENT_CERT != actual_content_type) { + failf(data, + "schannel: unexpected content type '%d' when extracting " + "certificate from CA file '%s'", + actual_content_type, ca_file_text); + result = CURLE_SSL_CACERT_BADFILE; + more_certs = 0; + } + else { + add_cert_result = + CertAddCertificateContextToStore(trust_store, + cert_context, + CERT_STORE_ADD_ALWAYS, + NULL); + CertFreeCertificateContext(cert_context); + if(!add_cert_result) { + char buffer[STRERROR_LEN]; + failf(data, + "schannel: failed to add certificate from CA file '%s' " + "to certificate store: %s", + ca_file_text, + Curl_winapi_strerror(GetLastError(), buffer, + sizeof(buffer))); + result = CURLE_SSL_CACERT_BADFILE; + more_certs = 0; + } + else { + num_certs++; + } + } + } + } + } + } + + if(result == CURLE_OK) { + if(!num_certs) { + infof(data, + "schannel: did not add any certificates from CA file '%s'", + ca_file_text); + } + else { + infof(data, + "schannel: added %d certificate(s) from CA file '%s'", + num_certs, ca_file_text); + } + } + return result; +} + +static CURLcode add_certs_file_to_store(HCERTSTORE trust_store, + const char *ca_file, + struct Curl_easy *data) +{ + CURLcode result; + HANDLE ca_file_handle = INVALID_HANDLE_VALUE; + LARGE_INTEGER file_size; + char *ca_file_buffer = NULL; + TCHAR *ca_file_tstr = NULL; + size_t ca_file_bufsize = 0; + DWORD total_bytes_read = 0; + + ca_file_tstr = curlx_convert_UTF8_to_tchar((char *)ca_file); + if(!ca_file_tstr) { + char buffer[STRERROR_LEN]; + failf(data, + "schannel: invalid path name for CA file '%s': %s", + ca_file, + Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + /* + * Read the CA file completely into memory before parsing it. This + * optimizes for the common case where the CA file will be relatively + * small ( < 1 MiB ). + */ + ca_file_handle = CreateFile(ca_file_tstr, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if(ca_file_handle == INVALID_HANDLE_VALUE) { + char buffer[STRERROR_LEN]; + failf(data, + "schannel: failed to open CA file '%s': %s", + ca_file, + Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + if(!GetFileSizeEx(ca_file_handle, &file_size)) { + char buffer[STRERROR_LEN]; + failf(data, + "schannel: failed to determine size of CA file '%s': %s", + ca_file, + Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + if(file_size.QuadPart > MAX_CAFILE_SIZE) { + failf(data, + "schannel: CA file exceeds max size of %u bytes", + MAX_CAFILE_SIZE); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + ca_file_bufsize = (size_t)file_size.QuadPart; + ca_file_buffer = (char *)malloc(ca_file_bufsize + 1); + if(!ca_file_buffer) { + result = CURLE_OUT_OF_MEMORY; + goto cleanup; + } + + while(total_bytes_read < ca_file_bufsize) { + DWORD bytes_to_read = (DWORD)(ca_file_bufsize - total_bytes_read); + DWORD bytes_read = 0; + + if(!ReadFile(ca_file_handle, ca_file_buffer + total_bytes_read, + bytes_to_read, &bytes_read, NULL)) { + char buffer[STRERROR_LEN]; + failf(data, + "schannel: failed to read from CA file '%s': %s", + ca_file, + Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + if(bytes_read == 0) { + /* Premature EOF -- adjust the bufsize to the new value */ + ca_file_bufsize = total_bytes_read; + } + else { + total_bytes_read += bytes_read; + } + } + + /* Null terminate the buffer */ + ca_file_buffer[ca_file_bufsize] = '\0'; + + result = add_certs_data_to_store(trust_store, + ca_file_buffer, ca_file_bufsize, + ca_file, + data); + +cleanup: + if(ca_file_handle != INVALID_HANDLE_VALUE) { + CloseHandle(ca_file_handle); + } + Curl_safefree(ca_file_buffer); + curlx_unicodefree(ca_file_tstr); + + return result; +} + +#endif /* HAS_MANUAL_VERIFY_API */ + +/* + * Returns the number of characters necessary to populate all the host_names. + * If host_names is not NULL, populate it with all the host names. Each string + * in the host_names is null-terminated and the last string is double + * null-terminated. If no DNS names are found, a single null-terminated empty + * string is returned. + */ +static DWORD cert_get_name_string(struct Curl_easy *data, + CERT_CONTEXT *cert_context, + LPTSTR host_names, + DWORD length) +{ + DWORD actual_length = 0; + BOOL compute_content = FALSE; + CERT_INFO *cert_info = NULL; + CERT_EXTENSION *extension = NULL; + CRYPT_DECODE_PARA decode_para = {0, 0, 0}; + CERT_ALT_NAME_INFO *alt_name_info = NULL; + DWORD alt_name_info_size = 0; + BOOL ret_val = FALSE; + LPTSTR current_pos = NULL; + DWORD i; + +#ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG + /* CERT_NAME_SEARCH_ALL_NAMES_FLAG is available from Windows 8 onwards. */ + if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + /* CertGetNameString will provide the 8-bit character string without + * any decoding */ + DWORD name_flags = + CERT_NAME_DISABLE_IE4_UTF8_FLAG | CERT_NAME_SEARCH_ALL_NAMES_FLAG; + actual_length = CertGetNameString(cert_context, + CERT_NAME_DNS_TYPE, + name_flags, + NULL, + host_names, + length); + return actual_length; + } +#endif + + compute_content = host_names != NULL && length != 0; + + /* Initialize default return values. */ + actual_length = 1; + if(compute_content) { + *host_names = '\0'; + } + + if(!cert_context) { + failf(data, "schannel: Null certificate context."); + return actual_length; + } + + cert_info = cert_context->pCertInfo; + if(!cert_info) { + failf(data, "schannel: Null certificate info."); + return actual_length; + } + + extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2, + cert_info->cExtension, + cert_info->rgExtension); + if(!extension) { + failf(data, "schannel: CertFindExtension() returned no extension."); + return actual_length; + } + + decode_para.cbSize = sizeof(CRYPT_DECODE_PARA); + + ret_val = + CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + szOID_SUBJECT_ALT_NAME2, + extension->Value.pbData, + extension->Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + &decode_para, + &alt_name_info, + &alt_name_info_size); + if(!ret_val) { + failf(data, + "schannel: CryptDecodeObjectEx() returned no alternate name " + "information."); + return actual_length; + } + + current_pos = host_names; + + /* Iterate over the alternate names and populate host_names. */ + for(i = 0; i < alt_name_info->cAltEntry; i++) { + const CERT_ALT_NAME_ENTRY *entry = &alt_name_info->rgAltEntry[i]; + wchar_t *dns_w = NULL; + size_t current_length = 0; + + if(entry->dwAltNameChoice != CERT_ALT_NAME_DNS_NAME) { + continue; + } + if(!entry->pwszDNSName) { + infof(data, "schannel: Empty DNS name."); + continue; + } + current_length = wcslen(entry->pwszDNSName) + 1; + if(!compute_content) { + actual_length += (DWORD)current_length; + continue; + } + /* Sanity check to prevent buffer overrun. */ + if((actual_length + current_length) > length) { + failf(data, "schannel: Not enough memory to list all host names."); + break; + } + dns_w = entry->pwszDNSName; + /* pwszDNSName is in ia5 string format and hence doesn't contain any + * non-ascii characters. */ + while(*dns_w != '\0') { + *current_pos++ = (char)(*dns_w++); + } + *current_pos++ = '\0'; + actual_length += (DWORD)current_length; + } + if(compute_content) { + /* Last string has double null-terminator. */ + *current_pos = '\0'; + } + return actual_length; +} + +/* Verify the server's hostname */ +CURLcode Curl_verify_host(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + SECURITY_STATUS sspi_status; + CURLcode result = CURLE_PEER_FAILED_VERIFICATION; + CERT_CONTEXT *pCertContextServer = NULL; + TCHAR *cert_hostname_buff = NULL; + size_t cert_hostname_buff_index = 0; + const char *conn_hostname = connssl->peer.hostname; + size_t hostlen = strlen(conn_hostname); + DWORD len = 0; + DWORD actual_len = 0; + + sspi_status = + s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &pCertContextServer); + + if((sspi_status != SEC_E_OK) || !pCertContextServer) { + char buffer[STRERROR_LEN]; + failf(data, "schannel: Failed to read remote certificate context: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + result = CURLE_PEER_FAILED_VERIFICATION; + goto cleanup; + } + + /* Determine the size of the string needed for the cert hostname */ + len = cert_get_name_string(data, pCertContextServer, NULL, 0); + if(len == 0) { + failf(data, + "schannel: CertGetNameString() returned no " + "certificate name information"); + result = CURLE_PEER_FAILED_VERIFICATION; + goto cleanup; + } + + /* CertGetNameString guarantees that the returned name will not contain + * embedded null bytes. This appears to be undocumented behavior. + */ + cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR)); + if(!cert_hostname_buff) { + result = CURLE_OUT_OF_MEMORY; + goto cleanup; + } + actual_len = cert_get_name_string( + data, pCertContextServer, (LPTSTR)cert_hostname_buff, len); + + /* Sanity check */ + if(actual_len != len) { + failf(data, + "schannel: CertGetNameString() returned certificate " + "name information of unexpected size"); + result = CURLE_PEER_FAILED_VERIFICATION; + goto cleanup; + } + + /* cert_hostname_buff contains all DNS names, where each name is + * null-terminated and the last DNS name is double null-terminated. Due to + * this encoding, use the length of the buffer to iterate over all names. + */ + result = CURLE_PEER_FAILED_VERIFICATION; + while(cert_hostname_buff_index < len && + cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') && + result == CURLE_PEER_FAILED_VERIFICATION) { + + char *cert_hostname; + + /* Comparing the cert name and the connection hostname encoded as UTF-8 + * is acceptable since both values are assumed to use ASCII + * (or some equivalent) encoding + */ + cert_hostname = curlx_convert_tchar_to_UTF8( + &cert_hostname_buff[cert_hostname_buff_index]); + if(!cert_hostname) { + result = CURLE_OUT_OF_MEMORY; + } + else { + if(Curl_cert_hostcheck(cert_hostname, strlen(cert_hostname), + conn_hostname, hostlen)) { + infof(data, + "schannel: connection hostname (%s) validated " + "against certificate name (%s)", + conn_hostname, cert_hostname); + result = CURLE_OK; + } + else { + size_t cert_hostname_len; + + infof(data, + "schannel: connection hostname (%s) did not match " + "against certificate name (%s)", + conn_hostname, cert_hostname); + + cert_hostname_len = + _tcslen(&cert_hostname_buff[cert_hostname_buff_index]); + + /* Move on to next cert name */ + cert_hostname_buff_index += cert_hostname_len + 1; + + result = CURLE_PEER_FAILED_VERIFICATION; + } + curlx_unicodefree(cert_hostname); + } + } + + if(result == CURLE_PEER_FAILED_VERIFICATION) { + failf(data, + "schannel: CertGetNameString() failed to match " + "connection hostname (%s) against server certificate names", + conn_hostname); + } + else if(result != CURLE_OK) + failf(data, "schannel: server certificate name verification failed"); + +cleanup: + Curl_safefree(cert_hostname_buff); + + if(pCertContextServer) + CertFreeCertificateContext(pCertContextServer); + + return result; +} + + +#ifdef HAS_MANUAL_VERIFY_API +/* Verify the server's certificate and hostname */ +CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + SECURITY_STATUS sspi_status; + CURLcode result = CURLE_OK; + CERT_CONTEXT *pCertContextServer = NULL; + const CERT_CHAIN_CONTEXT *pChainContext = NULL; + HCERTCHAINENGINE cert_chain_engine = NULL; + HCERTSTORE trust_store = NULL; + HCERTSTORE own_trust_store = NULL; + + DEBUGASSERT(BACKEND); + + sspi_status = + s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &pCertContextServer); + + if((sspi_status != SEC_E_OK) || !pCertContextServer) { + char buffer[STRERROR_LEN]; + failf(data, "schannel: Failed to read remote certificate context: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + result = CURLE_PEER_FAILED_VERIFICATION; + } + + if(result == CURLE_OK && + (conn_config->CAfile || conn_config->ca_info_blob) && + BACKEND->use_manual_cred_validation) { + /* + * Create a chain engine that uses the certificates in the CA file as + * trusted certificates. This is only supported on Windows 7+. + */ + + if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT, + VERSION_LESS_THAN)) { + failf(data, "schannel: this version of Windows is too old to support " + "certificate verification via CA bundle file."); + result = CURLE_SSL_CACERT_BADFILE; + } + else { + /* try cache */ + trust_store = Curl_schannel_get_cached_cert_store(cf, data); + + if(trust_store) { + infof(data, "schannel: reusing certificate store from cache"); + } + else { + /* Open the certificate store */ + trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY, + 0, + (HCRYPTPROV)NULL, + CERT_STORE_CREATE_NEW_FLAG, + NULL); + if(!trust_store) { + char buffer[STRERROR_LEN]; + failf(data, "schannel: failed to create certificate store: %s", + Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + result = CURLE_SSL_CACERT_BADFILE; + } + else { + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + own_trust_store = trust_store; + + if(ca_info_blob) { + result = add_certs_data_to_store(trust_store, + (const char *)ca_info_blob->data, + ca_info_blob->len, + "(memory blob)", + data); + } + else { + result = add_certs_file_to_store(trust_store, + conn_config->CAfile, + data); + } + if(result == CURLE_OK) { + if(Curl_schannel_set_cached_cert_store(cf, data, trust_store)) { + own_trust_store = NULL; + } + } + } + } + } + + if(result == CURLE_OK) { + struct cert_chain_engine_config_win7 engine_config; + BOOL create_engine_result; + + memset(&engine_config, 0, sizeof(engine_config)); + engine_config.cbSize = sizeof(engine_config); + engine_config.hExclusiveRoot = trust_store; + + /* CertCreateCertificateChainEngine will check the expected size of the + * CERT_CHAIN_ENGINE_CONFIG structure and fail if the specified size + * does not match the expected size. When this occurs, it indicates that + * CAINFO is not supported on the version of Windows in use. + */ + create_engine_result = + CertCreateCertificateChainEngine( + (CERT_CHAIN_ENGINE_CONFIG *)&engine_config, &cert_chain_engine); + if(!create_engine_result) { + char buffer[STRERROR_LEN]; + failf(data, + "schannel: failed to create certificate chain engine: %s", + Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + result = CURLE_SSL_CACERT_BADFILE; + } + } + } + + if(result == CURLE_OK) { + CERT_CHAIN_PARA ChainPara; + + memset(&ChainPara, 0, sizeof(ChainPara)); + ChainPara.cbSize = sizeof(ChainPara); + + if(!CertGetCertificateChain(cert_chain_engine, + pCertContextServer, + NULL, + pCertContextServer->hCertStore, + &ChainPara, + (ssl_config->no_revoke ? 0 : + CERT_CHAIN_REVOCATION_CHECK_CHAIN), + NULL, + &pChainContext)) { + char buffer[STRERROR_LEN]; + failf(data, "schannel: CertGetCertificateChain failed: %s", + Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + pChainContext = NULL; + result = CURLE_PEER_FAILED_VERIFICATION; + } + + if(result == CURLE_OK) { + CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0]; + DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED); + dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus; + + if(data->set.ssl.revoke_best_effort) { + /* Ignore errors when root certificates are missing the revocation + * list URL, or when the list could not be downloaded because the + * server is currently unreachable. */ + dwTrustErrorMask &= ~(DWORD)(CERT_TRUST_REVOCATION_STATUS_UNKNOWN | + CERT_TRUST_IS_OFFLINE_REVOCATION); + } + + if(dwTrustErrorMask) { + if(dwTrustErrorMask & CERT_TRUST_IS_REVOKED) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_IS_REVOKED"); + else if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_IS_PARTIAL_CHAIN"); + else if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_IS_UNTRUSTED_ROOT"); + else if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_IS_NOT_TIME_VALID"); + else if(dwTrustErrorMask & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_REVOCATION_STATUS_UNKNOWN"); + else + failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x", + dwTrustErrorMask); + result = CURLE_PEER_FAILED_VERIFICATION; + } + } + } + + if(result == CURLE_OK) { + if(conn_config->verifyhost) { + result = Curl_verify_host(cf, data); + } + } + + if(cert_chain_engine) { + CertFreeCertificateChainEngine(cert_chain_engine); + } + + if(own_trust_store) { + CertCloseStore(own_trust_store, 0); + } + + if(pChainContext) + CertFreeCertificateChain(pChainContext); + + if(pCertContextServer) + CertFreeCertificateContext(pCertContextServer); + + return result; +} + +#endif /* HAS_MANUAL_VERIFY_API */ +#endif /* USE_SCHANNEL */ diff --git a/Utilities/cmcurl/lib/vtls/sectransp.c b/Utilities/cmcurl/lib/vtls/sectransp.c new file mode 100644 index 0000000..0a22ff6 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/sectransp.c @@ -0,0 +1,3497 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Nick Zitzmann, <nickzman@gmail.com>. + * + * 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 + * + ***************************************************************************/ + +/* + * Source file for all iOS and macOS SecureTransport-specific code for the + * TLS/SSL layer. No code but vtls.c should ever call or use these functions. + */ + +#include "curl_setup.h" + +#include "urldata.h" /* for the Curl_easy definition */ +#include "curl_base64.h" +#include "strtok.h" +#include "multiif.h" +#include "strcase.h" +#include "x509asn1.h" +#include "strerror.h" + +#ifdef USE_SECTRANSP + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-pointer-compare" +#endif /* __clang__ */ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress" +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif + +#include <limits.h> + +#include <Security/Security.h> +/* For some reason, when building for iOS, the omnibus header above does + * not include SecureTransport.h as of iOS SDK 5.1. */ +#include <Security/SecureTransport.h> +#include <CoreFoundation/CoreFoundation.h> +#include <CommonCrypto/CommonDigest.h> + +/* The Security framework has changed greatly between iOS and different macOS + versions, and we will try to support as many of them as we can (back to + Leopard and iOS 5) by using macros and weak-linking. + + In general, you want to build this using the most recent OS SDK, since some + features require curl to be built against the latest SDK. TLS 1.1 and 1.2 + support, for instance, require the macOS 10.8 SDK or later. TLS 1.3 + requires the macOS 10.13 or iOS 11 SDK or later. */ +#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 +#error "The Secure Transport back-end requires Leopard or later." +#endif /* MAC_OS_X_VERSION_MAX_ALLOWED < 1050 */ + +#define CURL_BUILD_IOS 0 +#define CURL_BUILD_IOS_7 0 +#define CURL_BUILD_IOS_9 0 +#define CURL_BUILD_IOS_11 0 +#define CURL_BUILD_IOS_13 0 +#define CURL_BUILD_MAC 1 +/* This is the maximum API level we are allowed to use when building: */ +#define CURL_BUILD_MAC_10_5 MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 +#define CURL_BUILD_MAC_10_6 MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 +#define CURL_BUILD_MAC_10_7 MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 +#define CURL_BUILD_MAC_10_8 MAC_OS_X_VERSION_MAX_ALLOWED >= 1080 +#define CURL_BUILD_MAC_10_9 MAC_OS_X_VERSION_MAX_ALLOWED >= 1090 +#define CURL_BUILD_MAC_10_11 MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 +#define CURL_BUILD_MAC_10_13 MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 +#define CURL_BUILD_MAC_10_15 MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 +/* These macros mean "the following code is present to allow runtime backward + compatibility with at least this cat or earlier": + (You set this at build-time using the compiler command line option + "-mmacosx-version-min.") */ +#define CURL_SUPPORT_MAC_10_5 MAC_OS_X_VERSION_MIN_REQUIRED <= 1050 +#define CURL_SUPPORT_MAC_10_6 MAC_OS_X_VERSION_MIN_REQUIRED <= 1060 +#define CURL_SUPPORT_MAC_10_7 MAC_OS_X_VERSION_MIN_REQUIRED <= 1070 +#define CURL_SUPPORT_MAC_10_8 MAC_OS_X_VERSION_MIN_REQUIRED <= 1080 +#define CURL_SUPPORT_MAC_10_9 MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 + +#elif TARGET_OS_EMBEDDED || TARGET_OS_IPHONE +#define CURL_BUILD_IOS 1 +#define CURL_BUILD_IOS_7 __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 +#define CURL_BUILD_IOS_9 __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 +#define CURL_BUILD_IOS_11 __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 +#define CURL_BUILD_IOS_13 __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 +#define CURL_BUILD_MAC 0 +#define CURL_BUILD_MAC_10_5 0 +#define CURL_BUILD_MAC_10_6 0 +#define CURL_BUILD_MAC_10_7 0 +#define CURL_BUILD_MAC_10_8 0 +#define CURL_BUILD_MAC_10_9 0 +#define CURL_BUILD_MAC_10_11 0 +#define CURL_BUILD_MAC_10_13 0 +#define CURL_BUILD_MAC_10_15 0 +#define CURL_SUPPORT_MAC_10_5 0 +#define CURL_SUPPORT_MAC_10_6 0 +#define CURL_SUPPORT_MAC_10_7 0 +#define CURL_SUPPORT_MAC_10_8 0 +#define CURL_SUPPORT_MAC_10_9 0 + +#else +#error "The Secure Transport back-end requires iOS or macOS." +#endif /* (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) */ + +#if CURL_BUILD_MAC +#include <sys/sysctl.h> +#endif /* CURL_BUILD_MAC */ + +#include "sendf.h" +#include "inet_pton.h" +#include "connect.h" +#include "select.h" +#include "vtls.h" +#include "vtls_int.h" +#include "sectransp.h" +#include "curl_printf.h" +#include "strdup.h" + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + + +/* From MacTypes.h (which we can't include because it isn't present in iOS: */ +#define ioErr -36 +#define paramErr -50 + +struct st_ssl_backend_data { + SSLContextRef ssl_ctx; + bool ssl_direction; /* true if writing, false if reading */ + size_t ssl_write_buffered_length; +}; + +struct st_cipher { + const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */ + const char *alias_name; /* Alias name is the same as OpenSSL cipher name */ + SSLCipherSuite num; /* Cipher suite code/number defined in IANA registry */ + bool weak; /* Flag to mark cipher as weak based on previous implementation + of Secure Transport back-end by CURL */ +}; + +/* Macro to initialize st_cipher data structure: stringify id to name, cipher + number/id, 'weak' suite flag + */ +#define CIPHER_DEF(num, alias, weak) \ + { #num, alias, num, weak } + +/* + Macro to initialize st_cipher data structure with name, code (IANA cipher + number/id value), and 'weak' suite flag. The first 28 cipher suite numbers + have the same IANA code for both SSL and TLS standards: numbers 0x0000 to + 0x001B. They have different names though. The first 4 letters of the cipher + suite name are the protocol name: "SSL_" or "TLS_", rest of the IANA name is + the same for both SSL and TLS cipher suite name. + The second part of the problem is that macOS/iOS SDKs don't define all TLS + codes but only 12 of them. The SDK defines all SSL codes though, i.e. SSL_NUM + constant is always defined for those 28 ciphers while TLS_NUM is defined only + for 12 of the first 28 ciphers. Those 12 TLS cipher codes match to + corresponding SSL enum value and represent the same cipher suite. Therefore + we'll use the SSL enum value for those cipher suites because it is defined + for all 28 of them. + We make internal data consistent and based on TLS names, i.e. all st_cipher + item names start with the "TLS_" prefix. + Summarizing all the above, those 28 first ciphers are presented in our table + with both TLS and SSL names. Their cipher numbers are assigned based on the + SDK enum value for the SSL cipher, which matches to IANA TLS number. + */ +#define CIPHER_DEF_SSLTLS(num_wo_prefix, alias, weak) \ + { "TLS_" #num_wo_prefix, alias, SSL_##num_wo_prefix, weak } + +/* + Cipher suites were marked as weak based on the following: + RC4 encryption - rfc7465, the document contains a list of deprecated ciphers. + Marked in the code below as weak. + RC2 encryption - many mentions, was found vulnerable to a relatively easy + attack https://link.springer.com/chapter/10.1007%2F3-540-69710-1_14 + Marked in the code below as weak. + DES and IDEA encryption - rfc5469, has a list of deprecated ciphers. + Marked in the code below as weak. + Anonymous Diffie-Hellman authentication and anonymous elliptic curve + Diffie-Hellman - vulnerable to a man-in-the-middle attack. Deprecated by + RFC 4346 aka TLS 1.1 (section A.5, page 60) + Null bulk encryption suites - not encrypted communication + Export ciphers, i.e. ciphers with restrictions to be used outside the US for + software exported to some countries, they were excluded from TLS 1.1 + version. More precisely, they were noted as ciphers which MUST NOT be + negotiated in RFC 4346 aka TLS 1.1 (section A.5, pages 60 and 61). + All of those filters were considered weak because they contain a weak + algorithm like DES, RC2 or RC4, and already considered weak by other + criteria. + 3DES - NIST deprecated it and is going to retire it by 2023 + https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA + OpenSSL https://www.openssl.org/blog/blog/2016/08/24/sweet32/ also + deprecated those ciphers. Some other libraries also consider it + vulnerable or at least not strong enough. + + CBC ciphers are vulnerable with SSL3.0 and TLS1.0: + https://www.cisco.com/c/en/us/support/docs/security/email-security-appliance + /118518-technote-esa-00.html + We don't take care of this issue because it is resolved by later TLS + versions and for us, it requires more complicated checks, we need to + check a protocol version also. Vulnerability doesn't look very critical + and we do not filter out those cipher suites. + */ + +#define CIPHER_WEAK_NOT_ENCRYPTED TRUE +#define CIPHER_WEAK_RC_ENCRYPTION TRUE +#define CIPHER_WEAK_DES_ENCRYPTION TRUE +#define CIPHER_WEAK_IDEA_ENCRYPTION TRUE +#define CIPHER_WEAK_ANON_AUTH TRUE +#define CIPHER_WEAK_3DES_ENCRYPTION TRUE +#define CIPHER_STRONG_ENOUGH FALSE + +/* Please do not change the order of the first ciphers available for SSL. + Do not insert and do not delete any of them. Code below + depends on their order and continuity. + If you add a new cipher, please maintain order by number, i.e. + insert in between existing items to appropriate place based on + cipher suite IANA number +*/ +static const struct st_cipher ciphertable[] = { + /* SSL version 3.0 and initial TLS 1.0 cipher suites. + Defined since SDK 10.2.8 */ + CIPHER_DEF_SSLTLS(NULL_WITH_NULL_NULL, /* 0x0000 */ + NULL, + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF_SSLTLS(RSA_WITH_NULL_MD5, /* 0x0001 */ + "NULL-MD5", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF_SSLTLS(RSA_WITH_NULL_SHA, /* 0x0002 */ + "NULL-SHA", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_RC4_40_MD5, /* 0x0003 */ + "EXP-RC4-MD5", + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF_SSLTLS(RSA_WITH_RC4_128_MD5, /* 0x0004 */ + "RC4-MD5", + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF_SSLTLS(RSA_WITH_RC4_128_SHA, /* 0x0005 */ + "RC4-SHA", + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_RC2_CBC_40_MD5, /* 0x0006 */ + "EXP-RC2-CBC-MD5", + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF_SSLTLS(RSA_WITH_IDEA_CBC_SHA, /* 0x0007 */ + "IDEA-CBC-SHA", + CIPHER_WEAK_IDEA_ENCRYPTION), + CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x0008 */ + "EXP-DES-CBC-SHA", + CIPHER_WEAK_DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(RSA_WITH_DES_CBC_SHA, /* 0x0009 */ + "DES-CBC-SHA", + CIPHER_WEAK_DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ + "DES-CBC3-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DH_DSS_EXPORT_WITH_DES40_CBC_SHA, /* 0x000B */ + "EXP-DH-DSS-DES-CBC-SHA", + CIPHER_WEAK_DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DH_DSS_WITH_DES_CBC_SHA, /* 0x000C */ + "DH-DSS-DES-CBC-SHA", + CIPHER_WEAK_DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DH_DSS_WITH_3DES_EDE_CBC_SHA, /* 0x000D */ + "DH-DSS-DES-CBC3-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DH_RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x000E */ + "EXP-DH-RSA-DES-CBC-SHA", + CIPHER_WEAK_DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DH_RSA_WITH_DES_CBC_SHA, /* 0x000F */ + "DH-RSA-DES-CBC-SHA", + CIPHER_WEAK_DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x0010 */ + "DH-RSA-DES-CBC3-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, /* 0x0011 */ + "EXP-EDH-DSS-DES-CBC-SHA", + CIPHER_WEAK_DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DHE_DSS_WITH_DES_CBC_SHA, /* 0x0012 */ + "EDH-DSS-CBC-SHA", + CIPHER_WEAK_DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DHE_DSS_WITH_3DES_EDE_CBC_SHA, /* 0x0013 */ + "DHE-DSS-DES-CBC3-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x0014 */ + "EXP-EDH-RSA-DES-CBC-SHA", + CIPHER_WEAK_DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DHE_RSA_WITH_DES_CBC_SHA, /* 0x0015 */ + "EDH-RSA-DES-CBC-SHA", + CIPHER_WEAK_DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x0016 */ + "DHE-RSA-DES-CBC3-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF_SSLTLS(DH_anon_EXPORT_WITH_RC4_40_MD5, /* 0x0017 */ + "EXP-ADH-RC4-MD5", + CIPHER_WEAK_ANON_AUTH), + CIPHER_DEF_SSLTLS(DH_anon_WITH_RC4_128_MD5, /* 0x0018 */ + "ADH-RC4-MD5", + CIPHER_WEAK_ANON_AUTH), + CIPHER_DEF_SSLTLS(DH_anon_EXPORT_WITH_DES40_CBC_SHA, /* 0x0019 */ + "EXP-ADH-DES-CBC-SHA", + CIPHER_WEAK_ANON_AUTH), + CIPHER_DEF_SSLTLS(DH_anon_WITH_DES_CBC_SHA, /* 0x001A */ + "ADH-DES-CBC-SHA", + CIPHER_WEAK_ANON_AUTH), + CIPHER_DEF_SSLTLS(DH_anon_WITH_3DES_EDE_CBC_SHA, /* 0x001B */ + "ADH-DES-CBC3-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF(SSL_FORTEZZA_DMS_WITH_NULL_SHA, /* 0x001C */ + NULL, + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA, /* 0x001D */ + NULL, + CIPHER_STRONG_ENOUGH), + +#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 + /* RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */ + CIPHER_DEF(TLS_PSK_WITH_NULL_SHA, /* 0x002C */ + "PSK-NULL-SHA", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA, /* 0x002D */ + "DHE-PSK-NULL-SHA", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA, /* 0x002E */ + "RSA-PSK-NULL-SHA", + CIPHER_WEAK_NOT_ENCRYPTED), +#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ + + /* TLS addenda using AES, per RFC 3268. Defined since SDK 10.4u */ + CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ + "AES128-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_CBC_SHA, /* 0x0030 */ + "DH-DSS-AES128-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_CBC_SHA, /* 0x0031 */ + "DH-RSA-AES128-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_CBC_SHA, /* 0x0032 */ + "DHE-DSS-AES128-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_CBC_SHA, /* 0x0033 */ + "DHE-RSA-AES128-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_anon_WITH_AES_128_CBC_SHA, /* 0x0034 */ + "ADH-AES128-SHA", + CIPHER_WEAK_ANON_AUTH), + CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ + "AES256-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_CBC_SHA, /* 0x0036 */ + "DH-DSS-AES256-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_CBC_SHA, /* 0x0037 */ + "DH-RSA-AES256-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_CBC_SHA, /* 0x0038 */ + "DHE-DSS-AES256-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_CBC_SHA, /* 0x0039 */ + "DHE-RSA-AES256-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_anon_WITH_AES_256_CBC_SHA, /* 0x003A */ + "ADH-AES256-SHA", + CIPHER_WEAK_ANON_AUTH), + +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + /* TLS 1.2 addenda, RFC 5246 */ + /* Server provided RSA certificate for key exchange. */ + CIPHER_DEF(TLS_RSA_WITH_NULL_SHA256, /* 0x003B */ + "NULL-SHA256", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ + "AES128-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ + "AES256-SHA256", + CIPHER_STRONG_ENOUGH), + /* Server-authenticated (and optionally client-authenticated) + Diffie-Hellman. */ + CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_CBC_SHA256, /* 0x003E */ + "DH-DSS-AES128-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_CBC_SHA256, /* 0x003F */ + "DH-RSA-AES128-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, /* 0x0040 */ + "DHE-DSS-AES128-SHA256", + CIPHER_STRONG_ENOUGH), + + /* TLS 1.2 addenda, RFC 5246 */ + CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, /* 0x0067 */ + "DHE-RSA-AES128-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_CBC_SHA256, /* 0x0068 */ + "DH-DSS-AES256-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_CBC_SHA256, /* 0x0069 */ + "DH-RSA-AES256-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, /* 0x006A */ + "DHE-DSS-AES256-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, /* 0x006B */ + "DHE-RSA-AES256-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_anon_WITH_AES_128_CBC_SHA256, /* 0x006C */ + "ADH-AES128-SHA256", + CIPHER_WEAK_ANON_AUTH), + CIPHER_DEF(TLS_DH_anon_WITH_AES_256_CBC_SHA256, /* 0x006D */ + "ADH-AES256-SHA256", + CIPHER_WEAK_ANON_AUTH), +#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ + +#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 + /* Addendum from RFC 4279, TLS PSK */ + CIPHER_DEF(TLS_PSK_WITH_RC4_128_SHA, /* 0x008A */ + "PSK-RC4-SHA", + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF(TLS_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x008B */ + "PSK-3DES-EDE-CBC-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF(TLS_PSK_WITH_AES_128_CBC_SHA, /* 0x008C */ + "PSK-AES128-CBC-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_PSK_WITH_AES_256_CBC_SHA, /* 0x008D */ + "PSK-AES256-CBC-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_PSK_WITH_RC4_128_SHA, /* 0x008E */ + "DHE-PSK-RC4-SHA", + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF(TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x008F */ + "DHE-PSK-3DES-EDE-CBC-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_CBC_SHA, /* 0x0090 */ + "DHE-PSK-AES128-CBC-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_CBC_SHA, /* 0x0091 */ + "DHE-PSK-AES256-CBC-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_RSA_PSK_WITH_RC4_128_SHA, /* 0x0092 */ + "RSA-PSK-RC4-SHA", + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF(TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x0093 */ + "RSA-PSK-3DES-EDE-CBC-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_CBC_SHA, /* 0x0094 */ + "RSA-PSK-AES128-CBC-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_CBC_SHA, /* 0x0095 */ + "RSA-PSK-AES256-CBC-SHA", + CIPHER_STRONG_ENOUGH), +#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ + +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + /* Addenda from rfc 5288 AES Galois Counter Mode (GCM) Cipher Suites + for TLS. */ + CIPHER_DEF(TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ + "AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ + "AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, /* 0x009E */ + "DHE-RSA-AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, /* 0x009F */ + "DHE-RSA-AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_GCM_SHA256, /* 0x00A0 */ + "DH-RSA-AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_GCM_SHA384, /* 0x00A1 */ + "DH-RSA-AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, /* 0x00A2 */ + "DHE-DSS-AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, /* 0x00A3 */ + "DHE-DSS-AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_GCM_SHA256, /* 0x00A4 */ + "DH-DSS-AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_GCM_SHA384, /* 0x00A5 */ + "DH-DSS-AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DH_anon_WITH_AES_128_GCM_SHA256, /* 0x00A6 */ + "ADH-AES128-GCM-SHA256", + CIPHER_WEAK_ANON_AUTH), + CIPHER_DEF(TLS_DH_anon_WITH_AES_256_GCM_SHA384, /* 0x00A7 */ + "ADH-AES256-GCM-SHA384", + CIPHER_WEAK_ANON_AUTH), +#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ + +#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 + /* RFC 5487 - PSK with SHA-256/384 and AES GCM */ + CIPHER_DEF(TLS_PSK_WITH_AES_128_GCM_SHA256, /* 0x00A8 */ + "PSK-AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_PSK_WITH_AES_256_GCM_SHA384, /* 0x00A9 */ + "PSK-AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, /* 0x00AA */ + "DHE-PSK-AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, /* 0x00AB */ + "DHE-PSK-AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, /* 0x00AC */ + "RSA-PSK-AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, /* 0x00AD */ + "RSA-PSK-AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_PSK_WITH_AES_128_CBC_SHA256, /* 0x00AE */ + "PSK-AES128-CBC-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_PSK_WITH_AES_256_CBC_SHA384, /* 0x00AF */ + "PSK-AES256-CBC-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_PSK_WITH_NULL_SHA256, /* 0x00B0 */ + "PSK-NULL-SHA256", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_PSK_WITH_NULL_SHA384, /* 0x00B1 */ + "PSK-NULL-SHA384", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, /* 0x00B2 */ + "DHE-PSK-AES128-CBC-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, /* 0x00B3 */ + "DHE-PSK-AES256-CBC-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA256, /* 0x00B4 */ + "DHE-PSK-NULL-SHA256", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA384, /* 0x00B5 */ + "DHE-PSK-NULL-SHA384", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, /* 0x00B6 */ + "RSA-PSK-AES128-CBC-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, /* 0x00B7 */ + "RSA-PSK-AES256-CBC-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA256, /* 0x00B8 */ + "RSA-PSK-NULL-SHA256", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA384, /* 0x00B9 */ + "RSA-PSK-NULL-SHA384", + CIPHER_WEAK_NOT_ENCRYPTED), +#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ + + /* RFC 5746 - Secure Renegotiation. This is not a real suite, + it is a response to initiate negotiation again */ + CIPHER_DEF(TLS_EMPTY_RENEGOTIATION_INFO_SCSV, /* 0x00FF */ + NULL, + CIPHER_STRONG_ENOUGH), + +#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 + /* TLS 1.3 standard cipher suites for ChaCha20+Poly1305. + Note: TLS 1.3 ciphersuites do not specify the key exchange + algorithm -- they only specify the symmetric ciphers. + Cipher alias name matches to OpenSSL cipher name, and for + TLS 1.3 ciphers */ + CIPHER_DEF(TLS_AES_128_GCM_SHA256, /* 0x1301 */ + NULL, /* The OpenSSL cipher name matches to the IANA name */ + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_AES_256_GCM_SHA384, /* 0x1302 */ + NULL, /* The OpenSSL cipher name matches to the IANA name */ + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_CHACHA20_POLY1305_SHA256, /* 0x1303 */ + NULL, /* The OpenSSL cipher name matches to the IANA name */ + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_AES_128_CCM_SHA256, /* 0x1304 */ + NULL, /* The OpenSSL cipher name matches to the IANA name */ + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_AES_128_CCM_8_SHA256, /* 0x1305 */ + NULL, /* The OpenSSL cipher name matches to the IANA name */ + CIPHER_STRONG_ENOUGH), +#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ + +#if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS + /* ECDSA addenda, RFC 4492 */ + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_NULL_SHA, /* 0xC001 */ + "ECDH-ECDSA-NULL-SHA", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_RC4_128_SHA, /* 0xC002 */ + "ECDH-ECDSA-RC4-SHA", + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ + "ECDH-ECDSA-DES-CBC3-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ + "ECDH-ECDSA-AES128-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ + "ECDH-ECDSA-AES256-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_NULL_SHA, /* 0xC006 */ + "ECDHE-ECDSA-NULL-SHA", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, /* 0xC007 */ + "ECDHE-ECDSA-RC4-SHA", + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ + "ECDHE-ECDSA-DES-CBC3-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ + "ECDHE-ECDSA-AES128-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ + "ECDHE-ECDSA-AES256-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_RSA_WITH_NULL_SHA, /* 0xC00B */ + "ECDH-RSA-NULL-SHA", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_ECDH_RSA_WITH_RC4_128_SHA, /* 0xC00C */ + "ECDH-RSA-RC4-SHA", + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ + "ECDH-RSA-DES-CBC3-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ + "ECDH-RSA-AES128-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ + "ECDH-RSA-AES256-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_NULL_SHA, /* 0xC010 */ + "ECDHE-RSA-NULL-SHA", + CIPHER_WEAK_NOT_ENCRYPTED), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_RC4_128_SHA, /* 0xC011 */ + "ECDHE-RSA-RC4-SHA", + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ + "ECDHE-RSA-DES-CBC3-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ + "ECDHE-RSA-AES128-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ + "ECDHE-RSA-AES256-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_anon_WITH_NULL_SHA, /* 0xC015 */ + "AECDH-NULL-SHA", + CIPHER_WEAK_ANON_AUTH), + CIPHER_DEF(TLS_ECDH_anon_WITH_RC4_128_SHA, /* 0xC016 */ + "AECDH-RC4-SHA", + CIPHER_WEAK_ANON_AUTH), + CIPHER_DEF(TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, /* 0xC017 */ + "AECDH-DES-CBC3-SHA", + CIPHER_WEAK_3DES_ENCRYPTION), + CIPHER_DEF(TLS_ECDH_anon_WITH_AES_128_CBC_SHA, /* 0xC018 */ + "AECDH-AES128-SHA", + CIPHER_WEAK_ANON_AUTH), + CIPHER_DEF(TLS_ECDH_anon_WITH_AES_256_CBC_SHA, /* 0xC019 */ + "AECDH-AES256-SHA", + CIPHER_WEAK_ANON_AUTH), +#endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */ + +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with + HMAC SHA-256/384. */ + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ + "ECDHE-ECDSA-AES128-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ + "ECDHE-ECDSA-AES256-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ + "ECDH-ECDSA-AES128-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ + "ECDH-ECDSA-AES256-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ + "ECDHE-RSA-AES128-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ + "ECDHE-RSA-AES256-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ + "ECDH-RSA-AES128-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ + "ECDH-RSA-AES256-SHA384", + CIPHER_STRONG_ENOUGH), + /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with + SHA-256/384 and AES Galois Counter Mode (GCM) */ + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ + "ECDHE-ECDSA-AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ + "ECDHE-ECDSA-AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ + "ECDH-ECDSA-AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ + "ECDH-ECDSA-AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ + "ECDHE-RSA-AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ + "ECDHE-RSA-AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ + "ECDH-RSA-AES128-GCM-SHA256", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ + "ECDH-RSA-AES256-GCM-SHA384", + CIPHER_STRONG_ENOUGH), +#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ + +#if CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 + /* ECDHE_PSK Cipher Suites for Transport Layer Security (TLS), RFC 5489 */ + CIPHER_DEF(TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, /* 0xC035 */ + "ECDHE-PSK-AES128-CBC-SHA", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA, /* 0xC036 */ + "ECDHE-PSK-AES256-CBC-SHA", + CIPHER_STRONG_ENOUGH), +#endif /* CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 */ + +#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 + /* Addenda from rfc 7905 ChaCha20-Poly1305 Cipher Suites for + Transport Layer Security (TLS). */ + CIPHER_DEF(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ + "ECDHE-RSA-CHACHA20-POLY1305", + CIPHER_STRONG_ENOUGH), + CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ + "ECDHE-ECDSA-CHACHA20-POLY1305", + CIPHER_STRONG_ENOUGH), +#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ + +#if CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 + /* ChaCha20-Poly1305 Cipher Suites for Transport Layer Security (TLS), + RFC 7905 */ + CIPHER_DEF(TLS_PSK_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCAB */ + "PSK-CHACHA20-POLY1305", + CIPHER_STRONG_ENOUGH), +#endif /* CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 */ + + /* Tags for SSL 2 cipher kinds which are not specified for SSL 3. + Defined since SDK 10.2.8 */ + CIPHER_DEF(SSL_RSA_WITH_RC2_CBC_MD5, /* 0xFF80 */ + NULL, + CIPHER_WEAK_RC_ENCRYPTION), + CIPHER_DEF(SSL_RSA_WITH_IDEA_CBC_MD5, /* 0xFF81 */ + NULL, + CIPHER_WEAK_IDEA_ENCRYPTION), + CIPHER_DEF(SSL_RSA_WITH_DES_CBC_MD5, /* 0xFF82 */ + NULL, + CIPHER_WEAK_DES_ENCRYPTION), + CIPHER_DEF(SSL_RSA_WITH_3DES_EDE_CBC_MD5, /* 0xFF83 */ + NULL, + CIPHER_WEAK_3DES_ENCRYPTION), +}; + +#define NUM_OF_CIPHERS sizeof(ciphertable)/sizeof(ciphertable[0]) + + +/* pinned public key support tests */ + +/* version 1 supports macOS 10.12+ and iOS 10+ */ +#if ((TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000) || \ + (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)) +#define SECTRANSP_PINNEDPUBKEY_V1 1 +#endif + +/* version 2 supports MacOSX 10.7+ */ +#if (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070) +#define SECTRANSP_PINNEDPUBKEY_V2 1 +#endif + +#if defined(SECTRANSP_PINNEDPUBKEY_V1) || defined(SECTRANSP_PINNEDPUBKEY_V2) +/* this backend supports CURLOPT_PINNEDPUBLICKEY */ +#define SECTRANSP_PINNEDPUBKEY 1 +#endif /* SECTRANSP_PINNEDPUBKEY */ + +#ifdef SECTRANSP_PINNEDPUBKEY +/* both new and old APIs return rsa keys missing the spki header (not DER) */ +static const unsigned char rsa4096SpkiHeader[] = { + 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, + 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00}; + +static const unsigned char rsa2048SpkiHeader[] = { + 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, + 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00}; +#ifdef SECTRANSP_PINNEDPUBKEY_V1 +/* the *new* version doesn't return DER encoded ecdsa certs like the old... */ +static const unsigned char ecDsaSecp256r1SpkiHeader[] = { + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, + 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x42, 0x00}; + +static const unsigned char ecDsaSecp384r1SpkiHeader[] = { + 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, + 0x00, 0x22, 0x03, 0x62, 0x00}; +#endif /* SECTRANSP_PINNEDPUBKEY_V1 */ +#endif /* SECTRANSP_PINNEDPUBKEY */ + +static OSStatus sectransp_bio_cf_in_read(SSLConnectionRef connection, + void *buf, + size_t *dataLength) /* IN/OUT */ +{ + struct Curl_cfilter *cf = (struct Curl_cfilter *)connection; + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nread; + CURLcode result; + OSStatus rtn = noErr; + + DEBUGASSERT(data); + nread = Curl_conn_cf_recv(cf->next, data, buf, *dataLength, &result); + CURL_TRC_CF(data, cf, "bio_read(len=%zu) -> %zd, result=%d", + *dataLength, nread, result); + if(nread < 0) { + switch(result) { + case CURLE_OK: + case CURLE_AGAIN: + rtn = errSSLWouldBlock; + backend->ssl_direction = false; + break; + default: + rtn = ioErr; + break; + } + nread = 0; + } + else if(nread == 0) { + rtn = errSSLClosedGraceful; + } + else if((size_t)nread < *dataLength) { + rtn = errSSLWouldBlock; + } + *dataLength = nread; + return rtn; +} + +static OSStatus sectransp_bio_cf_out_write(SSLConnectionRef connection, + const void *buf, + size_t *dataLength) /* IN/OUT */ +{ + struct Curl_cfilter *cf = (struct Curl_cfilter *)connection; + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nwritten; + CURLcode result; + OSStatus rtn = noErr; + + DEBUGASSERT(data); + nwritten = Curl_conn_cf_send(cf->next, data, buf, *dataLength, &result); + CURL_TRC_CF(data, cf, "bio_send(len=%zu) -> %zd, result=%d", + *dataLength, nwritten, result); + if(nwritten <= 0) { + if(result == CURLE_AGAIN) { + rtn = errSSLWouldBlock; + backend->ssl_direction = true; + } + else { + rtn = ioErr; + } + nwritten = 0; + } + else if((size_t)nwritten < *dataLength) { + rtn = errSSLWouldBlock; + } + *dataLength = nwritten; + return rtn; +} + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +CF_INLINE const char *TLSCipherNameForNumber(SSLCipherSuite cipher) +{ + /* The first ciphers in the ciphertable are continuous. Here we do small + optimization and instead of loop directly get SSL name by cipher number. + */ + size_t i; + if(cipher <= SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA) { + return ciphertable[cipher].name; + } + /* Iterate through the rest of the ciphers */ + for(i = SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA + 1; i < NUM_OF_CIPHERS; + ++i) { + if(ciphertable[i].num == cipher) { + return ciphertable[i].name; + } + } + return ciphertable[SSL_NULL_WITH_NULL_NULL].name; +} +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + +#if CURL_BUILD_MAC +CF_INLINE void GetDarwinVersionNumber(int *major, int *minor) +{ + int mib[2]; + char *os_version; + size_t os_version_len; + char *os_version_major, *os_version_minor; + char *tok_buf; + + /* Get the Darwin kernel version from the kernel using sysctl(): */ + mib[0] = CTL_KERN; + mib[1] = KERN_OSRELEASE; + if(sysctl(mib, 2, NULL, &os_version_len, NULL, 0) == -1) + return; + os_version = malloc(os_version_len*sizeof(char)); + if(!os_version) + return; + if(sysctl(mib, 2, os_version, &os_version_len, NULL, 0) == -1) { + free(os_version); + return; + } + + /* Parse the version: */ + os_version_major = strtok_r(os_version, ".", &tok_buf); + os_version_minor = strtok_r(NULL, ".", &tok_buf); + *major = atoi(os_version_major); + *minor = atoi(os_version_minor); + free(os_version); +} +#endif /* CURL_BUILD_MAC */ + +/* Apple provides a myriad of ways of getting information about a certificate + into a string. Some aren't available under iOS or newer cats. So here's + a unified function for getting a string describing the certificate that + ought to work in all cats starting with Leopard. */ +CF_INLINE CFStringRef getsubject(SecCertificateRef cert) +{ + CFStringRef server_cert_summary = CFSTR("(null)"); + +#if CURL_BUILD_IOS + /* iOS: There's only one way to do this. */ + server_cert_summary = SecCertificateCopySubjectSummary(cert); +#else +#if CURL_BUILD_MAC_10_7 + /* Lion & later: Get the long description if we can. */ + if(SecCertificateCopyLongDescription) + server_cert_summary = + SecCertificateCopyLongDescription(NULL, cert, NULL); + else +#endif /* CURL_BUILD_MAC_10_7 */ +#if CURL_BUILD_MAC_10_6 + /* Snow Leopard: Get the certificate summary. */ + if(SecCertificateCopySubjectSummary) + server_cert_summary = SecCertificateCopySubjectSummary(cert); + else +#endif /* CURL_BUILD_MAC_10_6 */ + /* Leopard is as far back as we go... */ + (void)SecCertificateCopyCommonName(cert, &server_cert_summary); +#endif /* CURL_BUILD_IOS */ + return server_cert_summary; +} + +static CURLcode CopyCertSubject(struct Curl_easy *data, + SecCertificateRef cert, char **certp) +{ + CFStringRef c = getsubject(cert); + CURLcode result = CURLE_OK; + const char *direct; + char *cbuf = NULL; + *certp = NULL; + + if(!c) { + failf(data, "SSL: invalid CA certificate subject"); + return CURLE_PEER_FAILED_VERIFICATION; + } + + /* If the subject is already available as UTF-8 encoded (ie 'direct') then + use that, else convert it. */ + direct = CFStringGetCStringPtr(c, kCFStringEncodingUTF8); + if(direct) { + *certp = strdup(direct); + if(!*certp) { + failf(data, "SSL: out of memory"); + result = CURLE_OUT_OF_MEMORY; + } + } + else { + size_t cbuf_size = ((size_t)CFStringGetLength(c) * 4) + 1; + cbuf = calloc(1, cbuf_size); + if(cbuf) { + if(!CFStringGetCString(c, cbuf, cbuf_size, + kCFStringEncodingUTF8)) { + failf(data, "SSL: invalid CA certificate subject"); + result = CURLE_PEER_FAILED_VERIFICATION; + } + else + /* pass back the buffer */ + *certp = cbuf; + } + else { + failf(data, "SSL: couldn't allocate %zu bytes of memory", cbuf_size); + result = CURLE_OUT_OF_MEMORY; + } + } + if(result) + free(cbuf); + CFRelease(c); + return result; +} + +#if CURL_SUPPORT_MAC_10_6 +/* The SecKeychainSearch API was deprecated in Lion, and using it will raise + deprecation warnings, so let's not compile this unless it's necessary: */ +static OSStatus CopyIdentityWithLabelOldSchool(char *label, + SecIdentityRef *out_c_a_k) +{ + OSStatus status = errSecItemNotFound; + SecKeychainAttributeList attr_list; + SecKeychainAttribute attr; + SecKeychainSearchRef search = NULL; + SecCertificateRef cert = NULL; + + /* Set up the attribute list: */ + attr_list.count = 1L; + attr_list.attr = &attr; + + /* Set up our lone search criterion: */ + attr.tag = kSecLabelItemAttr; + attr.data = label; + attr.length = (UInt32)strlen(label); + + /* Start searching: */ + status = SecKeychainSearchCreateFromAttributes(NULL, + kSecCertificateItemClass, + &attr_list, + &search); + if(status == noErr) { + status = SecKeychainSearchCopyNext(search, + (SecKeychainItemRef *)&cert); + if(status == noErr && cert) { + /* If we found a certificate, does it have a private key? */ + status = SecIdentityCreateWithCertificate(NULL, cert, out_c_a_k); + CFRelease(cert); + } + } + + if(search) + CFRelease(search); + return status; +} +#endif /* CURL_SUPPORT_MAC_10_6 */ + +static OSStatus CopyIdentityWithLabel(char *label, + SecIdentityRef *out_cert_and_key) +{ + OSStatus status = errSecItemNotFound; + +#if CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS + CFArrayRef keys_list; + CFIndex keys_list_count; + CFIndex i; + + /* SecItemCopyMatching() was introduced in iOS and Snow Leopard. + kSecClassIdentity was introduced in Lion. If both exist, let's use them + to find the certificate. */ + if(SecItemCopyMatching && kSecClassIdentity) { + CFTypeRef keys[5]; + CFTypeRef values[5]; + CFDictionaryRef query_dict; + CFStringRef label_cf = CFStringCreateWithCString(NULL, label, + kCFStringEncodingUTF8); + + /* Set up our search criteria and expected results: */ + values[0] = kSecClassIdentity; /* we want a certificate and a key */ + keys[0] = kSecClass; + values[1] = kCFBooleanTrue; /* we want a reference */ + keys[1] = kSecReturnRef; + values[2] = kSecMatchLimitAll; /* kSecMatchLimitOne would be better if the + * label matching below worked correctly */ + keys[2] = kSecMatchLimit; + /* identity searches need a SecPolicyRef in order to work */ + values[3] = SecPolicyCreateSSL(false, NULL); + keys[3] = kSecMatchPolicy; + /* match the name of the certificate (doesn't work in macOS 10.12.1) */ + values[4] = label_cf; + keys[4] = kSecAttrLabel; + query_dict = CFDictionaryCreate(NULL, (const void **)keys, + (const void **)values, 5L, + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFRelease(values[3]); + + /* Do we have a match? */ + status = SecItemCopyMatching(query_dict, (CFTypeRef *) &keys_list); + + /* Because kSecAttrLabel matching doesn't work with kSecClassIdentity, + * we need to find the correct identity ourselves */ + if(status == noErr) { + keys_list_count = CFArrayGetCount(keys_list); + *out_cert_and_key = NULL; + status = 1; + for(i = 0; i<keys_list_count; i++) { + OSStatus err = noErr; + SecCertificateRef cert = NULL; + SecIdentityRef identity = + (SecIdentityRef) CFArrayGetValueAtIndex(keys_list, i); + err = SecIdentityCopyCertificate(identity, &cert); + if(err == noErr) { + CFStringRef common_name = NULL; + OSStatus copy_status = noErr; +#if CURL_BUILD_IOS + common_name = SecCertificateCopySubjectSummary(cert); +#elif CURL_BUILD_MAC_10_7 + copy_status = SecCertificateCopyCommonName(cert, &common_name); +#endif + if(copy_status == noErr && + CFStringCompare(common_name, label_cf, 0) == kCFCompareEqualTo) { + CFRelease(cert); + CFRelease(common_name); + CFRetain(identity); + *out_cert_and_key = identity; + status = noErr; + break; + } + if(common_name) + CFRelease(common_name); + } + CFRelease(cert); + } + } + + if(keys_list) + CFRelease(keys_list); + CFRelease(query_dict); + CFRelease(label_cf); + } + else { +#if CURL_SUPPORT_MAC_10_6 + /* On Leopard and Snow Leopard, fall back to SecKeychainSearch. */ + status = CopyIdentityWithLabelOldSchool(label, out_cert_and_key); +#endif /* CURL_SUPPORT_MAC_10_6 */ + } +#elif CURL_SUPPORT_MAC_10_6 + /* For developers building on older cats, we have no choice but to fall back + to SecKeychainSearch. */ + status = CopyIdentityWithLabelOldSchool(label, out_cert_and_key); +#endif /* CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS */ + return status; +} + +static OSStatus CopyIdentityFromPKCS12File(const char *cPath, + const struct curl_blob *blob, + const char *cPassword, + SecIdentityRef *out_cert_and_key) +{ + OSStatus status = errSecItemNotFound; + CFURLRef pkcs_url = NULL; + CFStringRef password = cPassword ? CFStringCreateWithCString(NULL, + cPassword, kCFStringEncodingUTF8) : NULL; + CFDataRef pkcs_data = NULL; + + /* We can import P12 files on iOS or OS X 10.7 or later: */ + /* These constants are documented as having first appeared in 10.6 but they + raise linker errors when used on that cat for some reason. */ +#if CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS + bool resource_imported; + + if(blob) { + pkcs_data = CFDataCreate(kCFAllocatorDefault, + (const unsigned char *)blob->data, blob->len); + status = (pkcs_data != NULL) ? errSecSuccess : errSecAllocate; + resource_imported = (pkcs_data != NULL); + } + else { + pkcs_url = + CFURLCreateFromFileSystemRepresentation(NULL, + (const UInt8 *)cPath, + strlen(cPath), false); + resource_imported = + CFURLCreateDataAndPropertiesFromResource(NULL, + pkcs_url, &pkcs_data, + NULL, NULL, &status); + } + + if(resource_imported) { + CFArrayRef items = NULL; + + /* On iOS SecPKCS12Import will never add the client certificate to the + * Keychain. + * + * It gives us back a SecIdentityRef that we can use directly. */ +#if CURL_BUILD_IOS + const void *cKeys[] = {kSecImportExportPassphrase}; + const void *cValues[] = {password}; + CFDictionaryRef options = CFDictionaryCreate(NULL, cKeys, cValues, + password ? 1L : 0L, NULL, NULL); + + if(options) { + status = SecPKCS12Import(pkcs_data, options, &items); + CFRelease(options); + } + + + /* On macOS SecPKCS12Import will always add the client certificate to + * the Keychain. + * + * As this doesn't match iOS, and apps may not want to see their client + * certificate saved in the user's keychain, we use SecItemImport + * with a NULL keychain to avoid importing it. + * + * This returns a SecCertificateRef from which we can construct a + * SecIdentityRef. + */ +#elif CURL_BUILD_MAC_10_7 + SecItemImportExportKeyParameters keyParams; + SecExternalFormat inputFormat = kSecFormatPKCS12; + SecExternalItemType inputType = kSecItemTypeCertificate; + + memset(&keyParams, 0x00, sizeof(keyParams)); + keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; + keyParams.passphrase = password; + + status = SecItemImport(pkcs_data, NULL, &inputFormat, &inputType, + 0, &keyParams, NULL, &items); +#endif + + + /* Extract the SecIdentityRef */ + if(status == errSecSuccess && items && CFArrayGetCount(items)) { + CFIndex i, count; + count = CFArrayGetCount(items); + + for(i = 0; i < count; i++) { + CFTypeRef item = (CFTypeRef) CFArrayGetValueAtIndex(items, i); + CFTypeID itemID = CFGetTypeID(item); + + if(itemID == CFDictionaryGetTypeID()) { + CFTypeRef identity = (CFTypeRef) CFDictionaryGetValue( + (CFDictionaryRef) item, + kSecImportItemIdentity); + CFRetain(identity); + *out_cert_and_key = (SecIdentityRef) identity; + break; + } +#if CURL_BUILD_MAC_10_7 + else if(itemID == SecCertificateGetTypeID()) { + status = SecIdentityCreateWithCertificate(NULL, + (SecCertificateRef) item, + out_cert_and_key); + break; + } +#endif + } + } + + if(items) + CFRelease(items); + CFRelease(pkcs_data); + } +#endif /* CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS */ + if(password) + CFRelease(password); + if(pkcs_url) + CFRelease(pkcs_url); + return status; +} + +/* This code was borrowed from nss.c, with some modifications: + * Determine whether the nickname passed in is a filename that needs to + * be loaded as a PEM or a nickname. + * + * returns 1 for a file + * returns 0 for not a file + */ +CF_INLINE bool is_file(const char *filename) +{ + struct_stat st; + + if(!filename) + return false; + + if(stat(filename, &st) == 0) + return S_ISREG(st.st_mode); + return false; +} + +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS +static CURLcode sectransp_version_from_curl(SSLProtocol *darwinver, + long ssl_version) +{ + switch(ssl_version) { + case CURL_SSLVERSION_TLSv1_0: + *darwinver = kTLSProtocol1; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_1: + *darwinver = kTLSProtocol11; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_2: + *darwinver = kTLSProtocol12; + return CURLE_OK; + case CURL_SSLVERSION_TLSv1_3: + /* TLS 1.3 support first appeared in iOS 11 and macOS 10.13 */ +#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 + if(__builtin_available(macOS 10.13, iOS 11.0, *)) { + *darwinver = kTLSProtocol13; + return CURLE_OK; + } +#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && + HAVE_BUILTIN_AVAILABLE == 1 */ + break; + } + return CURLE_SSL_CONNECT_ERROR; +} +#endif + +static CURLcode set_ssl_version_min_max(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + long ssl_version = conn_config->version; + long ssl_version_max = conn_config->version_max; + long max_supported_version_by_os; + + DEBUGASSERT(backend); + + /* macOS 10.5-10.7 supported TLS 1.0 only. + macOS 10.8 and later, and iOS 5 and later, added TLS 1.1 and 1.2. + macOS 10.13 and later, and iOS 11 and later, added TLS 1.3. */ +#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 + if(__builtin_available(macOS 10.13, iOS 11.0, *)) { + max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_3; + } + else { + max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_2; + } +#else + max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_2; +#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && + HAVE_BUILTIN_AVAILABLE == 1 */ + + switch(ssl_version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + ssl_version = CURL_SSLVERSION_TLSv1_0; + break; + } + + switch(ssl_version_max) { + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_DEFAULT: + ssl_version_max = max_supported_version_by_os; + break; + } + +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + if(SSLSetProtocolVersionMax) { + SSLProtocol darwin_ver_min = kTLSProtocol1; + SSLProtocol darwin_ver_max = kTLSProtocol1; + CURLcode result = sectransp_version_from_curl(&darwin_ver_min, + ssl_version); + if(result) { + failf(data, "unsupported min version passed via CURLOPT_SSLVERSION"); + return result; + } + result = sectransp_version_from_curl(&darwin_ver_max, + ssl_version_max >> 16); + if(result) { + failf(data, "unsupported max version passed via CURLOPT_SSLVERSION"); + return result; + } + + (void)SSLSetProtocolVersionMin(backend->ssl_ctx, darwin_ver_min); + (void)SSLSetProtocolVersionMax(backend->ssl_ctx, darwin_ver_max); + return result; + } + else { +#if CURL_SUPPORT_MAC_10_8 + long i = ssl_version; + (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, + kSSLProtocolAll, + false); + for(; i <= (ssl_version_max >> 16); i++) { + switch(i) { + case CURL_SSLVERSION_TLSv1_0: + (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, + kTLSProtocol1, + true); + break; + case CURL_SSLVERSION_TLSv1_1: + (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, + kTLSProtocol11, + true); + break; + case CURL_SSLVERSION_TLSv1_2: + (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, + kTLSProtocol12, + true); + break; + case CURL_SSLVERSION_TLSv1_3: + failf(data, "Your version of the OS does not support TLSv1.3"); + return CURLE_SSL_CONNECT_ERROR; + } + } + return CURLE_OK; +#endif /* CURL_SUPPORT_MAC_10_8 */ + } +#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ + failf(data, "Secure Transport: cannot set SSL protocol"); + return CURLE_SSL_CONNECT_ERROR; +} + +static bool is_cipher_suite_strong(SSLCipherSuite suite_num) +{ + size_t i; + for(i = 0; i < NUM_OF_CIPHERS; ++i) { + if(ciphertable[i].num == suite_num) { + return !ciphertable[i].weak; + } + } + /* If the cipher is not in our list, assume it is a new one + and therefore strong. Previous implementation was the same, + if cipher suite is not in the list, it was considered strong enough */ + return true; +} + +static bool is_separator(char c) +{ + /* Return whether character is a cipher list separator. */ + switch(c) { + case ' ': + case '\t': + case ':': + case ',': + case ';': + return true; + } + return false; +} + +static CURLcode sectransp_set_default_ciphers(struct Curl_easy *data, + SSLContextRef ssl_ctx) +{ + size_t all_ciphers_count = 0UL, allowed_ciphers_count = 0UL, i; + SSLCipherSuite *all_ciphers = NULL, *allowed_ciphers = NULL; + OSStatus err = noErr; + +#if CURL_BUILD_MAC + int darwinver_maj = 0, darwinver_min = 0; + + GetDarwinVersionNumber(&darwinver_maj, &darwinver_min); +#endif /* CURL_BUILD_MAC */ + + /* Disable cipher suites that ST supports but are not safe. These ciphers + are unlikely to be used in any case since ST gives other ciphers a much + higher priority, but it's probably better that we not connect at all than + to give the user a false sense of security if the server only supports + insecure ciphers. (Note: We don't care about SSLv2-only ciphers.) */ + err = SSLGetNumberSupportedCiphers(ssl_ctx, &all_ciphers_count); + if(err != noErr) { + failf(data, "SSL: SSLGetNumberSupportedCiphers() failed: OSStatus %d", + err); + return CURLE_SSL_CIPHER; + } + all_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite)); + if(!all_ciphers) { + failf(data, "SSL: Failed to allocate memory for all ciphers"); + return CURLE_OUT_OF_MEMORY; + } + allowed_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite)); + if(!allowed_ciphers) { + Curl_safefree(all_ciphers); + failf(data, "SSL: Failed to allocate memory for allowed ciphers"); + return CURLE_OUT_OF_MEMORY; + } + err = SSLGetSupportedCiphers(ssl_ctx, all_ciphers, + &all_ciphers_count); + if(err != noErr) { + Curl_safefree(all_ciphers); + Curl_safefree(allowed_ciphers); + return CURLE_SSL_CIPHER; + } + for(i = 0UL ; i < all_ciphers_count ; i++) { +#if CURL_BUILD_MAC + /* There's a known bug in early versions of Mountain Lion where ST's ECC + ciphers (cipher suite 0xC001 through 0xC032) simply do not work. + Work around the problem here by disabling those ciphers if we are + running in an affected version of OS X. */ + if(darwinver_maj == 12 && darwinver_min <= 3 && + all_ciphers[i] >= 0xC001 && all_ciphers[i] <= 0xC032) { + continue; + } +#endif /* CURL_BUILD_MAC */ + if(is_cipher_suite_strong(all_ciphers[i])) { + allowed_ciphers[allowed_ciphers_count++] = all_ciphers[i]; + } + } + err = SSLSetEnabledCiphers(ssl_ctx, allowed_ciphers, + allowed_ciphers_count); + Curl_safefree(all_ciphers); + Curl_safefree(allowed_ciphers); + if(err != noErr) { + failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err); + return CURLE_SSL_CIPHER; + } + return CURLE_OK; +} + +static CURLcode sectransp_set_selected_ciphers(struct Curl_easy *data, + SSLContextRef ssl_ctx, + const char *ciphers) +{ + size_t ciphers_count = 0; + const char *cipher_start = ciphers; + OSStatus err = noErr; + SSLCipherSuite selected_ciphers[NUM_OF_CIPHERS]; + + if(!ciphers) + return CURLE_OK; + + while(is_separator(*ciphers)) /* Skip initial separators. */ + ciphers++; + if(!*ciphers) + return CURLE_OK; + + cipher_start = ciphers; + while(*cipher_start && ciphers_count < NUM_OF_CIPHERS) { + bool cipher_found = FALSE; + size_t cipher_len = 0; + const char *cipher_end = NULL; + bool tls_name = FALSE; + size_t i; + + /* Skip separators */ + while(is_separator(*cipher_start)) + cipher_start++; + if(*cipher_start == '\0') { + break; + } + /* Find last position of a cipher in the ciphers string */ + cipher_end = cipher_start; + while(*cipher_end != '\0' && !is_separator(*cipher_end)) { + ++cipher_end; + } + + /* IANA cipher names start with the TLS_ or SSL_ prefix. + If the 4th symbol of the cipher is '_' we look for a cipher in the + table by its (TLS) name. + Otherwise, we try to match cipher by an alias. */ + if(cipher_start[3] == '_') { + tls_name = TRUE; + } + /* Iterate through the cipher table and look for the cipher, starting + the cipher number 0x01 because the 0x00 is not the real cipher */ + cipher_len = cipher_end - cipher_start; + for(i = 1; i < NUM_OF_CIPHERS; ++i) { + const char *table_cipher_name = NULL; + if(tls_name) { + table_cipher_name = ciphertable[i].name; + } + else if(ciphertable[i].alias_name) { + table_cipher_name = ciphertable[i].alias_name; + } + else { + continue; + } + /* Compare a part of the string between separators with a cipher name + in the table and make sure we matched the whole cipher name */ + if(strncmp(cipher_start, table_cipher_name, cipher_len) == 0 + && table_cipher_name[cipher_len] == '\0') { + selected_ciphers[ciphers_count] = ciphertable[i].num; + ++ciphers_count; + cipher_found = TRUE; + break; + } + } + if(!cipher_found) { + /* It would be more human-readable if we print the wrong cipher name + but we don't want to allocate any additional memory and copy the name + into it, then add it into logs. + Also, we do not modify an original cipher list string. We just point + to positions where cipher starts and ends in the cipher list string. + The message is a bit cryptic and longer than necessary but can be + understood by humans. */ + failf(data, "SSL: cipher string \"%s\" contains unsupported cipher name" + " starting position %zd and ending position %zd", + ciphers, + cipher_start - ciphers, + cipher_end - ciphers); + return CURLE_SSL_CIPHER; + } + if(*cipher_end) { + cipher_start = cipher_end + 1; + } + else { + break; + } + } + /* All cipher suites in the list are found. Report to logs as-is */ + infof(data, "SSL: Setting cipher suites list \"%s\"", ciphers); + + err = SSLSetEnabledCiphers(ssl_ctx, selected_ciphers, ciphers_count); + if(err != noErr) { + failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err); + return CURLE_SSL_CIPHER; + } + return CURLE_OK; +} + +static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + 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); + const struct curl_blob *ssl_cablob = conn_config->ca_info_blob; + const char * const ssl_cafile = + /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ + (ssl_cablob ? NULL : conn_config->CAfile); + const bool verifypeer = conn_config->verifypeer; + char * const ssl_cert = ssl_config->primary.clientcert; + const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; + char *ciphers; + OSStatus err = noErr; +#if CURL_BUILD_MAC + int darwinver_maj = 0, darwinver_min = 0; + + DEBUGASSERT(backend); + + CURL_TRC_CF(data, cf, "connect_step1"); + GetDarwinVersionNumber(&darwinver_maj, &darwinver_min); +#endif /* CURL_BUILD_MAC */ + +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + if(SSLCreateContext) { /* use the newer API if available */ + if(backend->ssl_ctx) + CFRelease(backend->ssl_ctx); + backend->ssl_ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); + if(!backend->ssl_ctx) { + failf(data, "SSL: couldn't create a context"); + return CURLE_OUT_OF_MEMORY; + } + } + else { + /* The old ST API does not exist under iOS, so don't compile it: */ +#if CURL_SUPPORT_MAC_10_8 + if(backend->ssl_ctx) + (void)SSLDisposeContext(backend->ssl_ctx); + err = SSLNewContext(false, &(backend->ssl_ctx)); + if(err != noErr) { + failf(data, "SSL: couldn't create a context: OSStatus %d", err); + return CURLE_OUT_OF_MEMORY; + } +#endif /* CURL_SUPPORT_MAC_10_8 */ + } +#else + if(backend->ssl_ctx) + (void)SSLDisposeContext(backend->ssl_ctx); + err = SSLNewContext(false, &(backend->ssl_ctx)); + if(err != noErr) { + failf(data, "SSL: couldn't create a context: OSStatus %d", err); + return CURLE_OUT_OF_MEMORY; + } +#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ + backend->ssl_write_buffered_length = 0UL; /* reset buffered write length */ + + /* check to see if we've been told to use an explicit SSL/TLS version */ +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + if(SSLSetProtocolVersionMax) { + switch(conn_config->version) { + case CURL_SSLVERSION_TLSv1: + (void)SSLSetProtocolVersionMin(backend->ssl_ctx, kTLSProtocol1); +#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 + if(__builtin_available(macOS 10.13, iOS 11.0, *)) { + (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol13); + } + else { + (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol12); + } +#else + (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol12); +#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && + HAVE_BUILTIN_AVAILABLE == 1 */ + break; + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + case CURL_SSLVERSION_TLSv1_3: + { + CURLcode result = set_ssl_version_min_max(cf, data); + if(result != CURLE_OK) + return result; + break; + } + case CURL_SSLVERSION_SSLv3: + case CURL_SSLVERSION_SSLv2: + failf(data, "SSL versions not supported"); + return CURLE_NOT_BUILT_IN; + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { +#if CURL_SUPPORT_MAC_10_8 + (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, + kSSLProtocolAll, + false); + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, + kTLSProtocol1, + true); + (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, + kTLSProtocol11, + true); + (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, + kTLSProtocol12, + true); + break; + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + case CURL_SSLVERSION_TLSv1_3: + { + CURLcode result = set_ssl_version_min_max(cf, data); + if(result != CURLE_OK) + return result; + break; + } + case CURL_SSLVERSION_SSLv3: + case CURL_SSLVERSION_SSLv2: + failf(data, "SSL versions not supported"); + return CURLE_NOT_BUILT_IN; + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } +#endif /* CURL_SUPPORT_MAC_10_8 */ + } +#else + if(conn_config->version_max != CURL_SSLVERSION_MAX_NONE) { + failf(data, "Your version of the OS does not support to set maximum" + " SSL/TLS version"); + return CURLE_SSL_CONNECT_ERROR; + } + (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, kSSLProtocolAll, false); + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, + kTLSProtocol1, + true); + break; + case CURL_SSLVERSION_TLSv1_1: + failf(data, "Your version of the OS does not support TLSv1.1"); + return CURLE_SSL_CONNECT_ERROR; + case CURL_SSLVERSION_TLSv1_2: + failf(data, "Your version of the OS does not support TLSv1.2"); + return CURLE_SSL_CONNECT_ERROR; + case CURL_SSLVERSION_TLSv1_3: + failf(data, "Your version of the OS does not support TLSv1.3"); + return CURLE_SSL_CONNECT_ERROR; + case CURL_SSLVERSION_SSLv2: + case CURL_SSLVERSION_SSLv3: + failf(data, "SSL versions not supported"); + return CURLE_NOT_BUILT_IN; + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } +#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ + +#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 + if(connssl->alpn) { + if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { + struct alpn_proto_buf proto; + size_t i; + CFStringRef cstr; + CFMutableArrayRef alpnArr = CFArrayCreateMutable(NULL, 0, + &kCFTypeArrayCallBacks); + for(i = 0; i < connssl->alpn->count; ++i) { + cstr = CFStringCreateWithCString(NULL, connssl->alpn->entries[i], + kCFStringEncodingUTF8); + if(!cstr) + return CURLE_OUT_OF_MEMORY; + CFArrayAppendValue(alpnArr, cstr); + CFRelease(cstr); + } + err = SSLSetALPNProtocols(backend->ssl_ctx, alpnArr); + if(err != noErr) + infof(data, "WARNING: failed to set ALPN protocols; OSStatus %d", + err); + CFRelease(alpnArr); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } + } +#endif + + if(ssl_config->key) { + infof(data, "WARNING: SSL: CURLOPT_SSLKEY is ignored by Secure " + "Transport. The private key must be in the Keychain."); + } + + if(ssl_cert || ssl_cert_blob) { + bool is_cert_data = ssl_cert_blob != NULL; + bool is_cert_file = (!is_cert_data) && is_file(ssl_cert); + SecIdentityRef cert_and_key = NULL; + + /* User wants to authenticate with a client cert. Look for it. Assume that + the user wants to use an identity loaded from the Keychain. If not, try + it as a file on disk */ + + if(!is_cert_data) + err = CopyIdentityWithLabel(ssl_cert, &cert_and_key); + else + err = !noErr; + if((err != noErr) && (is_cert_file || is_cert_data)) { + if(!ssl_config->cert_type) + infof(data, "SSL: Certificate type not set, assuming " + "PKCS#12 format."); + else if(!strcasecompare(ssl_config->cert_type, "P12")) { + failf(data, "SSL: The Security framework only supports " + "loading identities that are in PKCS#12 format."); + return CURLE_SSL_CERTPROBLEM; + } + + err = CopyIdentityFromPKCS12File(ssl_cert, ssl_cert_blob, + ssl_config->key_passwd, + &cert_and_key); + } + + if(err == noErr && cert_and_key) { + SecCertificateRef cert = NULL; + CFTypeRef certs_c[1]; + CFArrayRef certs; + + /* If we found one, print it out: */ + err = SecIdentityCopyCertificate(cert_and_key, &cert); + if(err == noErr) { + char *certp; + CURLcode result = CopyCertSubject(data, cert, &certp); + if(!result) { + infof(data, "Client certificate: %s", certp); + free(certp); + } + + CFRelease(cert); + if(result == CURLE_PEER_FAILED_VERIFICATION) + return CURLE_SSL_CERTPROBLEM; + if(result) + return result; + } + certs_c[0] = cert_and_key; + certs = CFArrayCreate(NULL, (const void **)certs_c, 1L, + &kCFTypeArrayCallBacks); + err = SSLSetCertificate(backend->ssl_ctx, certs); + if(certs) + CFRelease(certs); + if(err != noErr) { + failf(data, "SSL: SSLSetCertificate() failed: OSStatus %d", err); + return CURLE_SSL_CERTPROBLEM; + } + CFRelease(cert_and_key); + } + else { + const char *cert_showfilename_error = + is_cert_data ? "(memory blob)" : ssl_cert; + + switch(err) { + case errSecAuthFailed: case -25264: /* errSecPkcs12VerifyFailure */ + failf(data, "SSL: Incorrect password for the certificate \"%s\" " + "and its private key.", cert_showfilename_error); + break; + case -26275: /* errSecDecode */ case -25257: /* errSecUnknownFormat */ + failf(data, "SSL: Couldn't make sense of the data in the " + "certificate \"%s\" and its private key.", + cert_showfilename_error); + break; + case -25260: /* errSecPassphraseRequired */ + failf(data, "SSL The certificate \"%s\" requires a password.", + cert_showfilename_error); + break; + case errSecItemNotFound: + failf(data, "SSL: Can't find the certificate \"%s\" and its private " + "key in the Keychain.", cert_showfilename_error); + break; + default: + failf(data, "SSL: Can't load the certificate \"%s\" and its private " + "key: OSStatus %d", cert_showfilename_error, err); + break; + } + return CURLE_SSL_CERTPROBLEM; + } + } + + /* SSL always tries to verify the peer, this only says whether it should + * fail to connect if the verification fails, or if it should continue + * anyway. In the latter case the result of the verification is checked with + * SSL_get_verify_result() below. */ +#if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS + /* Snow Leopard introduced the SSLSetSessionOption() function, but due to + a library bug with the way the kSSLSessionOptionBreakOnServerAuth flag + works, it doesn't work as expected under Snow Leopard, Lion or + Mountain Lion. + So we need to call SSLSetEnableCertVerify() on those older cats in order + to disable certificate validation if the user turned that off. + (SecureTransport will always validate the certificate chain by + default.) + Note: + Darwin 11.x.x is Lion (10.7) + Darwin 12.x.x is Mountain Lion (10.8) + Darwin 13.x.x is Mavericks (10.9) + Darwin 14.x.x is Yosemite (10.10) + Darwin 15.x.x is El Capitan (10.11) + */ +#if CURL_BUILD_MAC + if(SSLSetSessionOption && darwinver_maj >= 13) { +#else + if(SSLSetSessionOption) { +#endif /* CURL_BUILD_MAC */ + bool break_on_auth = !conn_config->verifypeer || + ssl_cafile || ssl_cablob; + err = SSLSetSessionOption(backend->ssl_ctx, + kSSLSessionOptionBreakOnServerAuth, + break_on_auth); + if(err != noErr) { + failf(data, "SSL: SSLSetSessionOption() failed: OSStatus %d", err); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { +#if CURL_SUPPORT_MAC_10_8 + err = SSLSetEnableCertVerify(backend->ssl_ctx, + conn_config->verifypeer?true:false); + if(err != noErr) { + failf(data, "SSL: SSLSetEnableCertVerify() failed: OSStatus %d", err); + return CURLE_SSL_CONNECT_ERROR; + } +#endif /* CURL_SUPPORT_MAC_10_8 */ + } +#else + err = SSLSetEnableCertVerify(backend->ssl_ctx, + conn_config->verifypeer?true:false); + if(err != noErr) { + failf(data, "SSL: SSLSetEnableCertVerify() failed: OSStatus %d", err); + return CURLE_SSL_CONNECT_ERROR; + } +#endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */ + + if((ssl_cafile || ssl_cablob) && verifypeer) { + bool is_cert_data = ssl_cablob != NULL; + bool is_cert_file = (!is_cert_data) && is_file(ssl_cafile); + + if(!(is_cert_file || is_cert_data)) { + failf(data, "SSL: can't load CA certificate file %s", + ssl_cafile ? ssl_cafile : "(blob memory)"); + return CURLE_SSL_CACERT_BADFILE; + } + } + + /* Configure hostname check. SNI is used if available. + * Both hostname check and SNI require SSLSetPeerDomainName(). + * Also: the verifyhost setting influences SNI usage */ + if(conn_config->verifyhost) { + char *server = connssl->peer.sni? + connssl->peer.sni : connssl->peer.hostname; + err = SSLSetPeerDomainName(backend->ssl_ctx, server, strlen(server)); + + if(err != noErr) { + failf(data, "SSL: SSLSetPeerDomainName() failed: OSStatus %d", + err); + return CURLE_SSL_CONNECT_ERROR; + } + + if(connssl->peer.is_ip_address) { + infof(data, "WARNING: using IP address, SNI is being disabled by " + "the OS."); + } + } + else { + infof(data, "WARNING: disabling hostname validation also disables SNI."); + } + + ciphers = conn_config->cipher_list; + if(ciphers) { + err = sectransp_set_selected_ciphers(data, backend->ssl_ctx, ciphers); + } + else { + err = sectransp_set_default_ciphers(data, backend->ssl_ctx); + } + if(err != noErr) { + failf(data, "SSL: Unable to set ciphers for SSL/TLS handshake. " + "Error code: %d", err); + return CURLE_SSL_CIPHER; + } + +#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 + /* We want to enable 1/n-1 when using a CBC cipher unless the user + specifically doesn't want us doing that: */ + if(SSLSetSessionOption) { + SSLSetSessionOption(backend->ssl_ctx, kSSLSessionOptionSendOneByteRecord, + !ssl_config->enable_beast); + SSLSetSessionOption(backend->ssl_ctx, kSSLSessionOptionFalseStart, + ssl_config->falsestart); /* false start support */ + } +#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ + + /* Check if there's a cached ID we can/should use here! */ + if(ssl_config->primary.sessionid) { + char *ssl_sessionid; + size_t ssl_sessionid_len; + + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, (void **)&ssl_sessionid, + &ssl_sessionid_len)) { + /* we got a session id, use it! */ + err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len); + Curl_ssl_sessionid_unlock(data); + if(err != noErr) { + failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err); + return CURLE_SSL_CONNECT_ERROR; + } + /* Informational message */ + infof(data, "SSL reusing session ID"); + } + /* If there isn't one, then let's make one up! This has to be done prior + to starting the handshake. */ + else { + CURLcode result; + ssl_sessionid = + aprintf("%s:%d:%d:%s:%d", + ssl_cafile ? ssl_cafile : "(blob memory)", + verifypeer, conn_config->verifyhost, connssl->peer.hostname, + connssl->port); + ssl_sessionid_len = strlen(ssl_sessionid); + + err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len); + if(err != noErr) { + Curl_ssl_sessionid_unlock(data); + failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err); + return CURLE_SSL_CONNECT_ERROR; + } + + result = Curl_ssl_addsessionid(cf, data, ssl_sessionid, + ssl_sessionid_len, NULL); + Curl_ssl_sessionid_unlock(data); + if(result) { + failf(data, "failed to store ssl session"); + return result; + } + } + } + + err = SSLSetIOFuncs(backend->ssl_ctx, + sectransp_bio_cf_in_read, + sectransp_bio_cf_out_write); + if(err != noErr) { + failf(data, "SSL: SSLSetIOFuncs() failed: OSStatus %d", err); + return CURLE_SSL_CONNECT_ERROR; + } + + err = SSLSetConnection(backend->ssl_ctx, cf); + if(err != noErr) { + failf(data, "SSL: SSLSetConnection() failed: %d", err); + return CURLE_SSL_CONNECT_ERROR; + } + + connssl->connecting_state = ssl_connect_2; + return CURLE_OK; +} + +static long pem_to_der(const char *in, unsigned char **out, size_t *outlen) +{ + char *sep_start, *sep_end, *cert_start, *cert_end; + size_t i, j, err; + size_t len; + unsigned char *b64; + + /* Jump through the separators at the beginning of the certificate. */ + sep_start = strstr(in, "-----"); + if(!sep_start) + return 0; + cert_start = strstr(sep_start + 1, "-----"); + if(!cert_start) + return -1; + + cert_start += 5; + + /* Find separator after the end of the certificate. */ + cert_end = strstr(cert_start, "-----"); + if(!cert_end) + return -1; + + sep_end = strstr(cert_end + 1, "-----"); + if(!sep_end) + return -1; + sep_end += 5; + + len = cert_end - cert_start; + b64 = malloc(len + 1); + if(!b64) + return -1; + + /* Create base64 string without linefeeds. */ + for(i = 0, j = 0; i < len; i++) { + if(cert_start[i] != '\r' && cert_start[i] != '\n') + b64[j++] = cert_start[i]; + } + b64[j] = '\0'; + + err = Curl_base64_decode((const char *)b64, out, outlen); + free(b64); + if(err) { + free(*out); + return -1; + } + + return sep_end - in; +} + +#define MAX_CERTS_SIZE (50*1024*1024) /* arbitrary - to catch mistakes */ + +static int read_cert(const char *file, unsigned char **out, size_t *outlen) +{ + int fd; + ssize_t n; + unsigned char buf[512]; + struct dynbuf certs; + + Curl_dyn_init(&certs, MAX_CERTS_SIZE); + + fd = open(file, 0); + if(fd < 0) + return -1; + + for(;;) { + n = read(fd, buf, sizeof(buf)); + if(!n) + break; + if(n < 0) { + close(fd); + Curl_dyn_free(&certs); + return -1; + } + if(Curl_dyn_addn(&certs, buf, n)) { + close(fd); + return -1; + } + } + close(fd); + + *out = Curl_dyn_uptr(&certs); + *outlen = Curl_dyn_len(&certs); + + return 0; +} + +static int append_cert_to_array(struct Curl_easy *data, + const unsigned char *buf, size_t buflen, + CFMutableArrayRef array) +{ + char *certp; + CURLcode result; + SecCertificateRef cacert; + CFDataRef certdata; + + certdata = CFDataCreate(kCFAllocatorDefault, buf, buflen); + if(!certdata) { + failf(data, "SSL: failed to allocate array for CA certificate"); + return CURLE_OUT_OF_MEMORY; + } + + cacert = SecCertificateCreateWithData(kCFAllocatorDefault, certdata); + CFRelease(certdata); + if(!cacert) { + failf(data, "SSL: failed to create SecCertificate from CA certificate"); + return CURLE_SSL_CACERT_BADFILE; + } + + /* Check if cacert is valid. */ + result = CopyCertSubject(data, cacert, &certp); + switch(result) { + case CURLE_OK: + break; + case CURLE_PEER_FAILED_VERIFICATION: + return CURLE_SSL_CACERT_BADFILE; + case CURLE_OUT_OF_MEMORY: + default: + return result; + } + free(certp); + + CFArrayAppendValue(array, cacert); + CFRelease(cacert); + + return CURLE_OK; +} + +static CURLcode verify_cert_buf(struct Curl_cfilter *cf, + struct Curl_easy *data, + const unsigned char *certbuf, size_t buflen, + SSLContextRef ctx) +{ + int n = 0, rc; + long res; + unsigned char *der; + size_t derlen, offset = 0; + OSStatus ret; + SecTrustResultType trust_eval; + CFMutableArrayRef array = NULL; + SecTrustRef trust = NULL; + CURLcode result = CURLE_PEER_FAILED_VERIFICATION; + (void)cf; + /* + * Certbuf now contains the contents of the certificate file, which can be + * - a single DER certificate, + * - a single PEM certificate or + * - a bunch of PEM certificates (certificate bundle). + * + * Go through certbuf, and convert any PEM certificate in it into DER + * format. + */ + array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + if(!array) { + failf(data, "SSL: out of memory creating CA certificate array"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + while(offset < buflen) { + n++; + + /* + * Check if the certificate is in PEM format, and convert it to DER. If + * this fails, we assume the certificate is in DER format. + */ + res = pem_to_der((const char *)certbuf + offset, &der, &derlen); + if(res < 0) { + failf(data, "SSL: invalid CA certificate #%d (offset %zu) in bundle", + n, offset); + result = CURLE_SSL_CACERT_BADFILE; + goto out; + } + offset += res; + + if(res == 0 && offset == 0) { + /* This is not a PEM file, probably a certificate in DER format. */ + rc = append_cert_to_array(data, certbuf, buflen, array); + if(rc != CURLE_OK) { + CURL_TRC_CF(data, cf, "append_cert for CA failed"); + result = rc; + goto out; + } + break; + } + else if(res == 0) { + /* No more certificates in the bundle. */ + break; + } + + rc = append_cert_to_array(data, der, derlen, array); + free(der); + if(rc != CURLE_OK) { + CURL_TRC_CF(data, cf, "append_cert for CA failed"); + result = rc; + goto out; + } + } + + ret = SSLCopyPeerTrust(ctx, &trust); + if(!trust) { + failf(data, "SSL: error getting certificate chain"); + goto out; + } + else if(ret != noErr) { + failf(data, "SSLCopyPeerTrust() returned error %d", ret); + goto out; + } + + CURL_TRC_CF(data, cf, "setting %d trust anchors", n); + ret = SecTrustSetAnchorCertificates(trust, array); + if(ret != noErr) { + failf(data, "SecTrustSetAnchorCertificates() returned error %d", ret); + goto out; + } + ret = SecTrustSetAnchorCertificatesOnly(trust, true); + if(ret != noErr) { + failf(data, "SecTrustSetAnchorCertificatesOnly() returned error %d", ret); + goto out; + } + + trust_eval = 0; + ret = SecTrustEvaluate(trust, &trust_eval); + if(ret != noErr) { + failf(data, "SecTrustEvaluate() returned error %d", ret); + goto out; + } + + switch(trust_eval) { + case kSecTrustResultUnspecified: + /* what does this really mean? */ + CURL_TRC_CF(data, cf, "trust result: Unspecified"); + result = CURLE_OK; + goto out; + case kSecTrustResultProceed: + CURL_TRC_CF(data, cf, "trust result: Proceed"); + result = CURLE_OK; + goto out; + + case kSecTrustResultRecoverableTrustFailure: + failf(data, "SSL: peer not verified: RecoverableTrustFailure"); + goto out; + case kSecTrustResultDeny: + failf(data, "SSL: peer not verified: Deny"); + goto out; + default: + failf(data, "SSL: perr not verified: result=%d", trust_eval); + goto out; + } + +out: + if(trust) + CFRelease(trust); + if(array) + CFRelease(array); + return result; +} + +static CURLcode verify_cert(struct Curl_cfilter *cf, + struct Curl_easy *data, const char *cafile, + const struct curl_blob *ca_info_blob, + SSLContextRef ctx) +{ + int result; + unsigned char *certbuf; + size_t buflen; + + if(ca_info_blob) { + CURL_TRC_CF(data, cf, "verify_peer, CA from config blob"); + certbuf = (unsigned char *)malloc(ca_info_blob->len + 1); + if(!certbuf) { + return CURLE_OUT_OF_MEMORY; + } + buflen = ca_info_blob->len; + memcpy(certbuf, ca_info_blob->data, ca_info_blob->len); + certbuf[ca_info_blob->len]='\0'; + } + else if(cafile) { + CURL_TRC_CF(data, cf, "verify_peer, CA from file '%s'", cafile); + if(read_cert(cafile, &certbuf, &buflen) < 0) { + failf(data, "SSL: failed to read or invalid CA certificate"); + return CURLE_SSL_CACERT_BADFILE; + } + } + else + return CURLE_SSL_CACERT_BADFILE; + + result = verify_cert_buf(cf, data, certbuf, buflen, ctx); + free(certbuf); + return result; +} + + +#ifdef SECTRANSP_PINNEDPUBKEY +static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, + SSLContextRef ctx, + const char *pinnedpubkey) +{ /* Scratch */ + size_t pubkeylen, realpubkeylen, spkiHeaderLength = 24; + unsigned char *pubkey = NULL, *realpubkey = NULL; + const unsigned char *spkiHeader = NULL; + CFDataRef publicKeyBits = NULL; + + /* Result is returned to caller */ + CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; + + /* if a path wasn't specified, don't pin */ + if(!pinnedpubkey) + return CURLE_OK; + + + if(!ctx) + return result; + + do { + SecTrustRef trust; + OSStatus ret; + SecKeyRef keyRef; + + ret = SSLCopyPeerTrust(ctx, &trust); + if(ret != noErr || !trust) + break; + + keyRef = SecTrustCopyPublicKey(trust); + CFRelease(trust); + if(!keyRef) + break; + +#ifdef SECTRANSP_PINNEDPUBKEY_V1 + + publicKeyBits = SecKeyCopyExternalRepresentation(keyRef, NULL); + CFRelease(keyRef); + if(!publicKeyBits) + break; + +#elif SECTRANSP_PINNEDPUBKEY_V2 + + { + OSStatus success; + success = SecItemExport(keyRef, kSecFormatOpenSSL, 0, NULL, + &publicKeyBits); + CFRelease(keyRef); + if(success != errSecSuccess || !publicKeyBits) + break; + } + +#endif /* SECTRANSP_PINNEDPUBKEY_V2 */ + + pubkeylen = CFDataGetLength(publicKeyBits); + pubkey = (unsigned char *)CFDataGetBytePtr(publicKeyBits); + + switch(pubkeylen) { + case 526: + /* 4096 bit RSA pubkeylen == 526 */ + spkiHeader = rsa4096SpkiHeader; + break; + case 270: + /* 2048 bit RSA pubkeylen == 270 */ + spkiHeader = rsa2048SpkiHeader; + break; +#ifdef SECTRANSP_PINNEDPUBKEY_V1 + case 65: + /* ecDSA secp256r1 pubkeylen == 65 */ + spkiHeader = ecDsaSecp256r1SpkiHeader; + spkiHeaderLength = 26; + break; + case 97: + /* ecDSA secp384r1 pubkeylen == 97 */ + spkiHeader = ecDsaSecp384r1SpkiHeader; + spkiHeaderLength = 23; + break; + default: + infof(data, "SSL: unhandled public key length: %zu", pubkeylen); +#elif SECTRANSP_PINNEDPUBKEY_V2 + default: + /* ecDSA secp256r1 pubkeylen == 91 header already included? + * ecDSA secp384r1 header already included too + * we assume rest of algorithms do same, so do nothing + */ + result = Curl_pin_peer_pubkey(data, pinnedpubkey, pubkey, + pubkeylen); +#endif /* SECTRANSP_PINNEDPUBKEY_V2 */ + continue; /* break from loop */ + } + + realpubkeylen = pubkeylen + spkiHeaderLength; + realpubkey = malloc(realpubkeylen); + if(!realpubkey) + break; + + memcpy(realpubkey, spkiHeader, spkiHeaderLength); + memcpy(realpubkey + spkiHeaderLength, pubkey, pubkeylen); + + result = Curl_pin_peer_pubkey(data, pinnedpubkey, realpubkey, + realpubkeylen); + + } while(0); + + Curl_safefree(realpubkey); + if(publicKeyBits) + CFRelease(publicKeyBits); + + return result; +} +#endif /* SECTRANSP_PINNEDPUBKEY */ + +static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + OSStatus err; + SSLCipherSuite cipher; + SSLProtocol protocol = 0; + + DEBUGASSERT(ssl_connect_2 == connssl->connecting_state + || ssl_connect_2_reading == connssl->connecting_state + || ssl_connect_2_writing == connssl->connecting_state); + DEBUGASSERT(backend); + CURL_TRC_CF(data, cf, "connect_step2"); + + /* Here goes nothing: */ +check_handshake: + err = SSLHandshake(backend->ssl_ctx); + + if(err != noErr) { + switch(err) { + case errSSLWouldBlock: /* they're not done with us yet */ + connssl->connecting_state = backend->ssl_direction ? + ssl_connect_2_writing : ssl_connect_2_reading; + return CURLE_OK; + + /* The below is errSSLServerAuthCompleted; it's not defined in + Leopard's headers */ + case -9841: + if((conn_config->CAfile || conn_config->ca_info_blob) && + conn_config->verifypeer) { + CURLcode result = verify_cert(cf, data, conn_config->CAfile, + conn_config->ca_info_blob, + backend->ssl_ctx); + if(result) + return result; + } + /* the documentation says we need to call SSLHandshake() again */ + goto check_handshake; + + /* Problem with encrypt / decrypt */ + case errSSLPeerDecodeError: + failf(data, "Decode failed"); + break; + case errSSLDecryptionFail: + case errSSLPeerDecryptionFail: + failf(data, "Decryption failed"); + break; + case errSSLPeerDecryptError: + failf(data, "A decryption error occurred"); + break; + case errSSLBadCipherSuite: + failf(data, "A bad SSL cipher suite was encountered"); + break; + case errSSLCrypto: + failf(data, "An underlying cryptographic error was encountered"); + break; +#if CURL_BUILD_MAC_10_11 || CURL_BUILD_IOS_9 + case errSSLWeakPeerEphemeralDHKey: + failf(data, "Indicates a weak ephemeral Diffie-Hellman key"); + break; +#endif + + /* Problem with the message record validation */ + case errSSLBadRecordMac: + case errSSLPeerBadRecordMac: + failf(data, "A record with a bad message authentication code (MAC) " + "was encountered"); + break; + case errSSLRecordOverflow: + case errSSLPeerRecordOverflow: + failf(data, "A record overflow occurred"); + break; + + /* Problem with zlib decompression */ + case errSSLPeerDecompressFail: + failf(data, "Decompression failed"); + break; + + /* Problem with access */ + case errSSLPeerAccessDenied: + failf(data, "Access was denied"); + break; + case errSSLPeerInsufficientSecurity: + failf(data, "There is insufficient security for this operation"); + break; + + /* These are all certificate problems with the server: */ + case errSSLXCertChainInvalid: + failf(data, "SSL certificate problem: Invalid certificate chain"); + return CURLE_PEER_FAILED_VERIFICATION; + case errSSLUnknownRootCert: + failf(data, "SSL certificate problem: Untrusted root certificate"); + return CURLE_PEER_FAILED_VERIFICATION; + case errSSLNoRootCert: + failf(data, "SSL certificate problem: No root certificate"); + return CURLE_PEER_FAILED_VERIFICATION; + case errSSLCertNotYetValid: + failf(data, "SSL certificate problem: The certificate chain had a " + "certificate that is not yet valid"); + return CURLE_PEER_FAILED_VERIFICATION; + case errSSLCertExpired: + case errSSLPeerCertExpired: + failf(data, "SSL certificate problem: Certificate chain had an " + "expired certificate"); + return CURLE_PEER_FAILED_VERIFICATION; + case errSSLBadCert: + case errSSLPeerBadCert: + failf(data, "SSL certificate problem: Couldn't understand the server " + "certificate format"); + return CURLE_PEER_FAILED_VERIFICATION; + case errSSLPeerUnsupportedCert: + failf(data, "SSL certificate problem: An unsupported certificate " + "format was encountered"); + return CURLE_PEER_FAILED_VERIFICATION; + case errSSLPeerCertRevoked: + failf(data, "SSL certificate problem: The certificate was revoked"); + return CURLE_PEER_FAILED_VERIFICATION; + case errSSLPeerCertUnknown: + failf(data, "SSL certificate problem: The certificate is unknown"); + return CURLE_PEER_FAILED_VERIFICATION; + + /* These are all certificate problems with the client: */ + case errSecAuthFailed: + failf(data, "SSL authentication failed"); + break; + case errSSLPeerHandshakeFail: + failf(data, "SSL peer handshake failed, the server most likely " + "requires a client certificate to connect"); + break; + case errSSLPeerUnknownCA: + failf(data, "SSL server rejected the client certificate due to " + "the certificate being signed by an unknown certificate " + "authority"); + break; + + /* This error is raised if the server's cert didn't match the server's + host name: */ + case errSSLHostNameMismatch: + failf(data, "SSL certificate peer verification failed, the " + "certificate did not match \"%s\"\n", connssl->peer.dispname); + return CURLE_PEER_FAILED_VERIFICATION; + + /* Problem with SSL / TLS negotiation */ + case errSSLNegotiation: + failf(data, "Could not negotiate an SSL cipher suite with the server"); + break; + case errSSLBadConfiguration: + failf(data, "A configuration error occurred"); + break; + case errSSLProtocol: + failf(data, "SSL protocol error"); + break; + case errSSLPeerProtocolVersion: + failf(data, "A bad protocol version was encountered"); + break; + case errSSLPeerNoRenegotiation: + failf(data, "No renegotiation is allowed"); + break; + + /* Generic handshake errors: */ + case errSSLConnectionRefused: + failf(data, "Server dropped the connection during the SSL handshake"); + break; + case errSSLClosedAbort: + failf(data, "Server aborted the SSL handshake"); + break; + case errSSLClosedGraceful: + failf(data, "The connection closed gracefully"); + break; + case errSSLClosedNoNotify: + failf(data, "The server closed the session with no notification"); + break; + /* Sometimes paramErr happens with buggy ciphers: */ + case paramErr: + case errSSLInternal: + case errSSLPeerInternalError: + failf(data, "Internal SSL engine error encountered during the " + "SSL handshake"); + break; + case errSSLFatalAlert: + failf(data, "Fatal SSL engine error encountered during the SSL " + "handshake"); + break; + /* Unclassified error */ + case errSSLBufferOverflow: + failf(data, "An insufficient buffer was provided"); + break; + case errSSLIllegalParam: + failf(data, "An illegal parameter was encountered"); + break; + case errSSLModuleAttach: + failf(data, "Module attach failure"); + break; + case errSSLSessionNotFound: + failf(data, "An attempt to restore an unknown session failed"); + break; + case errSSLPeerExportRestriction: + failf(data, "An export restriction occurred"); + break; + case errSSLPeerUserCancelled: + failf(data, "The user canceled the operation"); + break; + case errSSLPeerUnexpectedMsg: + failf(data, "Peer rejected unexpected message"); + break; +#if CURL_BUILD_MAC_10_11 || CURL_BUILD_IOS_9 + /* Treating non-fatal error as fatal like before */ + case errSSLClientHelloReceived: + failf(data, "A non-fatal result for providing a server name " + "indication"); + break; +#endif + + /* Error codes defined in the enum but should never be returned. + We list them here just in case. */ +#if CURL_BUILD_MAC_10_6 + /* Only returned when kSSLSessionOptionBreakOnCertRequested is set */ + case errSSLClientCertRequested: + failf(data, "Server requested a client certificate during the " + "handshake"); + return CURLE_SSL_CLIENTCERT; +#endif +#if CURL_BUILD_MAC_10_9 + /* Alias for errSSLLast, end of error range */ + case errSSLUnexpectedRecord: + failf(data, "Unexpected (skipped) record in DTLS"); + break; +#endif + default: + /* May also return codes listed in Security Framework Result Codes */ + failf(data, "Unknown SSL protocol error in connection to %s:%d", + connssl->peer.hostname, err); + break; + } + return CURLE_SSL_CONNECT_ERROR; + } + else { + /* we have been connected fine, we're not waiting for anything else. */ + connssl->connecting_state = ssl_connect_3; + +#ifdef SECTRANSP_PINNEDPUBKEY + if(data->set.str[STRING_SSL_PINNEDPUBLICKEY]) { + CURLcode result = + pkp_pin_peer_pubkey(data, backend->ssl_ctx, + data->set.str[STRING_SSL_PINNEDPUBLICKEY]); + if(result) { + failf(data, "SSL: public key does not match pinned public key"); + return result; + } + } +#endif /* SECTRANSP_PINNEDPUBKEY */ + + /* Informational message */ + (void)SSLGetNegotiatedCipher(backend->ssl_ctx, &cipher); + (void)SSLGetNegotiatedProtocolVersion(backend->ssl_ctx, &protocol); + switch(protocol) { + case kSSLProtocol2: + infof(data, "SSL 2.0 connection using %s", + TLSCipherNameForNumber(cipher)); + break; + case kSSLProtocol3: + infof(data, "SSL 3.0 connection using %s", + TLSCipherNameForNumber(cipher)); + break; + case kTLSProtocol1: + infof(data, "TLS 1.0 connection using %s", + TLSCipherNameForNumber(cipher)); + break; +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + case kTLSProtocol11: + infof(data, "TLS 1.1 connection using %s", + TLSCipherNameForNumber(cipher)); + break; + case kTLSProtocol12: + infof(data, "TLS 1.2 connection using %s", + TLSCipherNameForNumber(cipher)); + break; +#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ +#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 + case kTLSProtocol13: + infof(data, "TLS 1.3 connection using %s", + TLSCipherNameForNumber(cipher)); + break; +#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ + default: + infof(data, "Unknown protocol connection"); + break; + } + +#if(CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 + if(connssl->alpn) { + if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { + CFArrayRef alpnArr = NULL; + CFStringRef chosenProtocol = NULL; + err = SSLCopyALPNProtocols(backend->ssl_ctx, &alpnArr); + + if(err == noErr && alpnArr && CFArrayGetCount(alpnArr) >= 1) + chosenProtocol = CFArrayGetValueAtIndex(alpnArr, 0); + +#ifdef USE_HTTP2 + if(chosenProtocol && + !CFStringCompare(chosenProtocol, CFSTR(ALPN_H2), 0)) { + cf->conn->alpn = CURL_HTTP_VERSION_2; + } + else +#endif + if(chosenProtocol && + !CFStringCompare(chosenProtocol, CFSTR(ALPN_HTTP_1_1), 0)) { + cf->conn->alpn = CURL_HTTP_VERSION_1_1; + } + else + infof(data, VTLS_INFOF_NO_ALPN); + + Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? + BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + + /* chosenProtocol is a reference to the string within alpnArr + and doesn't need to be freed separately */ + if(alpnArr) + CFRelease(alpnArr); + } + } +#endif + + return CURLE_OK; + } +} + +static CURLcode +add_cert_to_certinfo(struct Curl_easy *data, + SecCertificateRef server_cert, + int idx) +{ + CURLcode result = CURLE_OK; + const char *beg; + const char *end; + CFDataRef cert_data = SecCertificateCopyData(server_cert); + + if(!cert_data) + return CURLE_PEER_FAILED_VERIFICATION; + + beg = (const char *)CFDataGetBytePtr(cert_data); + end = beg + CFDataGetLength(cert_data); + result = Curl_extract_certinfo(data, idx, beg, end); + CFRelease(cert_data); + return result; +} + +static CURLcode +collect_server_cert_single(struct Curl_cfilter *cf, struct Curl_easy *data, + SecCertificateRef server_cert, + CFIndex idx) +{ + CURLcode result = CURLE_OK; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(data->set.verbose) { + char *certp; + result = CopyCertSubject(data, server_cert, &certp); + if(!result) { + infof(data, "Server certificate: %s", certp); + free(certp); + } + } +#endif + if(ssl_config->certinfo) + result = add_cert_to_certinfo(data, server_cert, (int)idx); + return result; +} + +/* This should be called during step3 of the connection at the earliest */ +static CURLcode collect_server_cert(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + const bool show_verbose_server_cert = data->set.verbose; +#else + const bool show_verbose_server_cert = false; +#endif + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + CURLcode result = ssl_config->certinfo ? + CURLE_PEER_FAILED_VERIFICATION : CURLE_OK; + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + CFArrayRef server_certs = NULL; + SecCertificateRef server_cert; + OSStatus err; + CFIndex i, count; + SecTrustRef trust = NULL; + + DEBUGASSERT(backend); + + if(!show_verbose_server_cert && !ssl_config->certinfo) + return CURLE_OK; + + if(!backend->ssl_ctx) + return result; + +#if CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS +#if CURL_BUILD_IOS +#pragma unused(server_certs) + err = SSLCopyPeerTrust(backend->ssl_ctx, &trust); + /* For some reason, SSLCopyPeerTrust() can return noErr and yet return + a null trust, so be on guard for that: */ + if(err == noErr && trust) { + count = SecTrustGetCertificateCount(trust); + if(ssl_config->certinfo) + result = Curl_ssl_init_certinfo(data, (int)count); + for(i = 0L ; !result && (i < count) ; i++) { + server_cert = SecTrustGetCertificateAtIndex(trust, i); + result = collect_server_cert_single(cf, data, server_cert, i); + } + CFRelease(trust); + } +#else + /* SSLCopyPeerCertificates() is deprecated as of Mountain Lion. + The function SecTrustGetCertificateAtIndex() is officially present + in Lion, but it is unfortunately also present in Snow Leopard as + private API and doesn't work as expected. So we have to look for + a different symbol to make sure this code is only executed under + Lion or later. */ + if(SecTrustCopyPublicKey) { +#pragma unused(server_certs) + err = SSLCopyPeerTrust(backend->ssl_ctx, &trust); + /* For some reason, SSLCopyPeerTrust() can return noErr and yet return + a null trust, so be on guard for that: */ + if(err == noErr && trust) { + count = SecTrustGetCertificateCount(trust); + if(ssl_config->certinfo) + result = Curl_ssl_init_certinfo(data, (int)count); + for(i = 0L ; !result && (i < count) ; i++) { + server_cert = SecTrustGetCertificateAtIndex(trust, i); + result = collect_server_cert_single(cf, data, server_cert, i); + } + CFRelease(trust); + } + } + else { +#if CURL_SUPPORT_MAC_10_8 + err = SSLCopyPeerCertificates(backend->ssl_ctx, &server_certs); + /* Just in case SSLCopyPeerCertificates() returns null too... */ + if(err == noErr && server_certs) { + count = CFArrayGetCount(server_certs); + if(ssl_config->certinfo) + result = Curl_ssl_init_certinfo(data, (int)count); + for(i = 0L ; !result && (i < count) ; i++) { + server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, + i); + result = collect_server_cert_single(cf, data, server_cert, i); + } + CFRelease(server_certs); + } +#endif /* CURL_SUPPORT_MAC_10_8 */ + } +#endif /* CURL_BUILD_IOS */ +#else +#pragma unused(trust) + err = SSLCopyPeerCertificates(backend->ssl_ctx, &server_certs); + if(err == noErr) { + count = CFArrayGetCount(server_certs); + if(ssl_config->certinfo) + result = Curl_ssl_init_certinfo(data, (int)count); + for(i = 0L ; !result && (i < count) ; i++) { + server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, i); + result = collect_server_cert_single(cf, data, server_cert, i); + } + CFRelease(server_certs); + } +#endif /* CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS */ + return result; +} + +static CURLcode sectransp_connect_step3(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + CURLcode result; + + CURL_TRC_CF(data, cf, "connect_step3"); + /* There is no step 3! + * Well, okay, let's collect server certificates, and if verbose mode is on, + * let's print the details of the server certificates. */ + result = collect_server_cert(cf, data); + if(result) + return result; + + connssl->connecting_state = ssl_connect_done; + return CURLE_OK; +} + +static CURLcode +sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, + bool nonblocking, + bool *done) +{ + CURLcode result; + struct ssl_connect_data *connssl = cf->ctx; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + int what; + + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + if(ssl_connect_1 == connssl->connecting_state) { + /* Find out how much more time we're allowed */ + const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + result = sectransp_connect_step1(cf, data); + if(result) + return result; + } + + while(ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + + /* check allowed time left */ + const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + if(connssl->connecting_state == ssl_connect_2_reading || + connssl->connecting_state == ssl_connect_2_writing) { + + curl_socket_t writefd = ssl_connect_2_writing == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, + nonblocking ? 0 : timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) { + *done = FALSE; + return CURLE_OK; + } + else { + /* timeout */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + /* Run transaction, and return to the caller if it failed or if this + * connection is done nonblocking and this loop would execute again. This + * permits the owner of a multi handle to abort a connection attempt + * before step2 has completed while ensuring that a client using select() + * or epoll() will always have a valid fdset to wait on. + */ + result = sectransp_connect_step2(cf, data); + if(result || (nonblocking && + (ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state))) + return result; + + } /* repeat step2 until all transactions are done. */ + + + if(ssl_connect_3 == connssl->connecting_state) { + result = sectransp_connect_step3(cf, data); + if(result) + return result; + } + + if(ssl_connect_done == connssl->connecting_state) { + CURL_TRC_CF(data, cf, "connected"); + connssl->state = ssl_connection_complete; + *done = TRUE; + } + else + *done = FALSE; + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + + return CURLE_OK; +} + +static CURLcode sectransp_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + return sectransp_connect_common(cf, data, TRUE, done); +} + +static CURLcode sectransp_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result; + bool done = FALSE; + + result = sectransp_connect_common(cf, data, FALSE, &done); + + if(result) + return result; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + + (void) data; + + DEBUGASSERT(backend); + + if(backend->ssl_ctx) { + CURL_TRC_CF(data, cf, "close"); + (void)SSLClose(backend->ssl_ctx); +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + if(SSLCreateContext) + CFRelease(backend->ssl_ctx); +#if CURL_SUPPORT_MAC_10_8 + else + (void)SSLDisposeContext(backend->ssl_ctx); +#endif /* CURL_SUPPORT_MAC_10_8 */ +#else + (void)SSLDisposeContext(backend->ssl_ctx); +#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ + backend->ssl_ctx = NULL; + } +} + +static int sectransp_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + ssize_t nread; + int what; + int rc; + char buf[120]; + int loop = 10; /* avoid getting stuck */ + CURLcode result; + + DEBUGASSERT(backend); + + if(!backend->ssl_ctx) + return 0; + +#ifndef CURL_DISABLE_FTP + if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE) + return 0; +#endif + + sectransp_close(cf, data); + + rc = 0; + + what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), + SSL_SHUTDOWN_TIMEOUT); + + CURL_TRC_CF(data, cf, "shutdown"); + while(loop--) { + if(what < 0) { + /* anything that gets here is fatally bad */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + rc = -1; + break; + } + + if(!what) { /* timeout */ + failf(data, "SSL shutdown timeout"); + break; + } + + /* Something to read, let's do it and hope that it is the close + notify alert from the server. No way to SSL_Read now, so use read(). */ + + nread = Curl_conn_cf_recv(cf->next, data, buf, sizeof(buf), &result); + + if(nread < 0) { + failf(data, "read: %s", curl_easy_strerror(result)); + rc = -1; + } + + if(nread <= 0) + break; + + what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0); + } + + return rc; +} + +static void sectransp_session_free(void *ptr) +{ + /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a + cached session ID inside the Security framework. There is a private + function that does this, but I don't want to have to explain to you why I + got your application rejected from the App Store due to the use of a + private API, so the best we can do is free up our own char array that we + created way back in sectransp_connect_step1... */ + Curl_safefree(ptr); +} + +static size_t sectransp_version(char *buffer, size_t size) +{ + return msnprintf(buffer, size, "SecureTransport"); +} + +static bool sectransp_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + const struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + OSStatus err; + size_t buffer; + + (void)data; + DEBUGASSERT(backend); + + if(backend->ssl_ctx) { /* SSL is in use */ + CURL_TRC_CF((struct Curl_easy *)data, cf, "data_pending"); + err = SSLGetBufferedReadSize(backend->ssl_ctx, &buffer); + if(err == noErr) + return buffer > 0UL; + return false; + } + else + return false; +} + +static CURLcode sectransp_random(struct Curl_easy *data UNUSED_PARAM, + unsigned char *entropy, size_t length) +{ + /* arc4random_buf() isn't available on cats older than Lion, so let's + do this manually for the benefit of the older cats. */ + size_t i; + u_int32_t random_number = 0; + + (void)data; + + for(i = 0 ; i < length ; i++) { + if(i % sizeof(u_int32_t) == 0) + random_number = arc4random(); + entropy[i] = random_number & 0xFF; + random_number >>= 8; + } + i = random_number = 0; + return CURLE_OK; +} + +static CURLcode sectransp_sha256sum(const unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *sha256sum, /* output */ + size_t sha256len) +{ + (void)sha256len; + assert(sha256len >= CURL_SHA256_DIGEST_LENGTH); + (void)CC_SHA256(tmp, (CC_LONG)tmplen, sha256sum); + return CURLE_OK; +} + +static bool sectransp_false_start(void) +{ +#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 + if(SSLSetSessionOption) + return TRUE; +#endif + return FALSE; +} + +static ssize_t sectransp_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, + size_t len, + CURLcode *curlcode) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + size_t processed = 0UL; + OSStatus err; + + DEBUGASSERT(backend); + + /* The SSLWrite() function works a little differently than expected. The + fourth argument (processed) is currently documented in Apple's + documentation as: "On return, the length, in bytes, of the data actually + written." + + Now, one could interpret that as "written to the socket," but actually, + it returns the amount of data that was written to a buffer internal to + the SSLContextRef instead. So it's possible for SSLWrite() to return + errSSLWouldBlock and a number of bytes "written" because those bytes were + encrypted and written to a buffer, not to the socket. + + So if this happens, then we need to keep calling SSLWrite() over and + over again with no new data until it quits returning errSSLWouldBlock. */ + + /* Do we have buffered data to write from the last time we were called? */ + if(backend->ssl_write_buffered_length) { + /* Write the buffered data: */ + err = SSLWrite(backend->ssl_ctx, NULL, 0UL, &processed); + switch(err) { + case noErr: + /* processed is always going to be 0 because we didn't write to + the buffer, so return how much was written to the socket */ + processed = backend->ssl_write_buffered_length; + backend->ssl_write_buffered_length = 0UL; + break; + case errSSLWouldBlock: /* argh, try again */ + *curlcode = CURLE_AGAIN; + return -1L; + default: + failf(data, "SSLWrite() returned error %d", err); + *curlcode = CURLE_SEND_ERROR; + return -1L; + } + } + else { + /* We've got new data to write: */ + err = SSLWrite(backend->ssl_ctx, mem, len, &processed); + if(err != noErr) { + switch(err) { + case errSSLWouldBlock: + /* Data was buffered but not sent, we have to tell the caller + to try sending again, and remember how much was buffered */ + backend->ssl_write_buffered_length = len; + *curlcode = CURLE_AGAIN; + return -1L; + default: + failf(data, "SSLWrite() returned error %d", err); + *curlcode = CURLE_SEND_ERROR; + return -1L; + } + } + } + return (ssize_t)processed; +} + +static ssize_t sectransp_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, + size_t buffersize, + CURLcode *curlcode) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + size_t processed = 0UL; + OSStatus err; + + DEBUGASSERT(backend); + +again: + *curlcode = CURLE_OK; + err = SSLRead(backend->ssl_ctx, buf, buffersize, &processed); + + if(err != noErr) { + switch(err) { + case errSSLWouldBlock: /* return how much we read (if anything) */ + if(processed) { + return (ssize_t)processed; + } + *curlcode = CURLE_AGAIN; + return -1L; + + /* errSSLClosedGraceful - server gracefully shut down the SSL session + errSSLClosedNoNotify - server hung up on us instead of sending a + closure alert notice, read() is returning 0 + Either way, inform the caller that the server disconnected. */ + case errSSLClosedGraceful: + case errSSLClosedNoNotify: + *curlcode = CURLE_OK; + return 0; + + /* The below is errSSLPeerAuthCompleted; it's not defined in + Leopard's headers */ + case -9841: + if((conn_config->CAfile || conn_config->ca_info_blob) && + conn_config->verifypeer) { + CURLcode result = verify_cert(cf, data, conn_config->CAfile, + conn_config->ca_info_blob, + backend->ssl_ctx); + if(result) { + *curlcode = result; + return -1; + } + } + goto again; + default: + failf(data, "SSLRead() return error %d", err); + *curlcode = CURLE_RECV_ERROR; + return -1L; + } + } + return (ssize_t)processed; +} + +static void *sectransp_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) +{ + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + (void)info; + DEBUGASSERT(backend); + return backend->ssl_ctx; +} + +const struct Curl_ssl Curl_ssl_sectransp = { + { CURLSSLBACKEND_SECURETRANSPORT, "secure-transport" }, /* info */ + + SSLSUPP_CAINFO_BLOB | + SSLSUPP_CERTINFO | +#ifdef SECTRANSP_PINNEDPUBKEY + SSLSUPP_PINNEDPUBKEY | +#endif /* SECTRANSP_PINNEDPUBKEY */ + SSLSUPP_HTTPS_PROXY, + + sizeof(struct st_ssl_backend_data), + + Curl_none_init, /* init */ + Curl_none_cleanup, /* cleanup */ + sectransp_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + sectransp_shutdown, /* shutdown */ + sectransp_data_pending, /* data_pending */ + sectransp_random, /* random */ + Curl_none_cert_status_request, /* cert_status_request */ + sectransp_connect, /* connect */ + sectransp_connect_nonblocking, /* connect_nonblocking */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ + sectransp_get_internals, /* get_internals */ + sectransp_close, /* close_one */ + Curl_none_close_all, /* close_all */ + sectransp_session_free, /* session_free */ + Curl_none_set_engine, /* set_engine */ + Curl_none_set_engine_default, /* set_engine_default */ + Curl_none_engines_list, /* engines_list */ + sectransp_false_start, /* false_start */ + sectransp_sha256sum, /* sha256sum */ + NULL, /* associate_connection */ + NULL, /* disassociate_connection */ + NULL, /* free_multi_ssl_backend_data */ + sectransp_recv, /* recv decrypted data */ + sectransp_send, /* send data to encrypt */ +}; + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif /* USE_SECTRANSP */ diff --git a/Utilities/cmcurl/lib/vtls/sectransp.h b/Utilities/cmcurl/lib/vtls/sectransp.h new file mode 100644 index 0000000..0f1085a --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/sectransp.h @@ -0,0 +1,34 @@ +#ifndef HEADER_CURL_SECTRANSP_H +#define HEADER_CURL_SECTRANSP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Nick Zitzmann, <nickzman@gmail.com>. + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_SECTRANSP + +extern const struct Curl_ssl Curl_ssl_sectransp; + +#endif /* USE_SECTRANSP */ +#endif /* HEADER_CURL_SECTRANSP_H */ diff --git a/Utilities/cmcurl/lib/vtls/vtls.c b/Utilities/cmcurl/lib/vtls/vtls.c new file mode 100644 index 0000000..34eda3e --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/vtls.c @@ -0,0 +1,2157 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* This file is for implementing all "generic" SSL functions that all libcurl + internals should use. It is then responsible for calling the proper + "backend" function. + + SSL-functions in libcurl should call functions in this source file, and not + to any specific SSL-layer. + + Curl_ssl_ - prefix for generic ones + + Note that this source code uses the functions of the configured SSL + backend via the global Curl_ssl instance. + + "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 <sys/types.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#include "urldata.h" +#include "cfilters.h" + +#include "vtls.h" /* generic SSL protos etc */ +#include "vtls_int.h" +#include "slist.h" +#include "sendf.h" +#include "strcase.h" +#include "url.h" +#include "progress.h" +#include "share.h" +#include "multiif.h" +#include "timeval.h" +#include "curl_md5.h" +#include "warnless.h" +#include "curl_base64.h" +#include "curl_printf.h" +#include "inet_pton.h" +#include "strdup.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + + +/* convenience macro to check if this handle is using a shared SSL session */ +#define SSLSESSION_SHARED(data) (data->share && \ + (data->share->specifier & \ + (1<<CURL_LOCK_DATA_SSL_SESSION))) + +#define CLONE_STRING(var) \ + do { \ + if(source->var) { \ + dest->var = strdup(source->var); \ + if(!dest->var) \ + return FALSE; \ + } \ + else \ + dest->var = NULL; \ + } while(0) + +#define CLONE_BLOB(var) \ + do { \ + if(blobdup(&dest->var, source->var)) \ + return FALSE; \ + } while(0) + +static CURLcode blobdup(struct curl_blob **dest, + struct curl_blob *src) +{ + DEBUGASSERT(dest); + DEBUGASSERT(!*dest); + if(src) { + /* only if there's data to dupe! */ + struct curl_blob *d; + d = malloc(sizeof(struct curl_blob) + src->len); + if(!d) + return CURLE_OUT_OF_MEMORY; + d->len = src->len; + /* Always duplicate because the connection may survive longer than the + handle that passed in the blob. */ + d->flags = CURL_BLOB_COPY; + d->data = (void *)((char *)d + sizeof(struct curl_blob)); + memcpy(d->data, src->data, src->len); + *dest = d; + } + return CURLE_OK; +} + +/* returns TRUE if the blobs are identical */ +static bool blobcmp(struct curl_blob *first, struct curl_blob *second) +{ + if(!first && !second) /* both are NULL */ + return TRUE; + if(!first || !second) /* one is NULL */ + return FALSE; + if(first->len != second->len) /* different sizes */ + return FALSE; + return !memcmp(first->data, second->data, first->len); /* same data */ +} + +#ifdef USE_SSL +static const struct alpn_spec ALPN_SPEC_H11 = { + { ALPN_HTTP_1_1 }, 1 +}; +#ifdef USE_HTTP2 +static const struct alpn_spec ALPN_SPEC_H2_H11 = { + { ALPN_H2, ALPN_HTTP_1_1 }, 2 +}; +#endif + +static const struct alpn_spec *alpn_get_spec(int httpwant, bool use_alpn) +{ + if(!use_alpn) + return NULL; +#ifdef USE_HTTP2 + if(httpwant >= CURL_HTTP_VERSION_2) + return &ALPN_SPEC_H2_H11; +#else + (void)httpwant; +#endif + /* Use the ALPN protocol "http/1.1" for HTTP/1.x. + Avoid "http/1.0" because some servers don't support it. */ + return &ALPN_SPEC_H11; +} +#endif /* USE_SSL */ + + +void Curl_ssl_easy_config_init(struct Curl_easy *data) +{ + /* + * libcurl 7.10 introduced SSL verification *by default*! This needs to be + * switched off unless wanted. + */ + data->set.ssl.primary.verifypeer = TRUE; + data->set.ssl.primary.verifyhost = TRUE; + data->set.ssl.primary.sessionid = TRUE; /* session ID caching by default */ +#ifndef CURL_DISABLE_PROXY + data->set.proxy_ssl = data->set.ssl; +#endif +} + +static bool +match_ssl_primary_config(struct Curl_easy *data, + struct ssl_primary_config *c1, + struct ssl_primary_config *c2) +{ + (void)data; + if((c1->version == c2->version) && + (c1->version_max == c2->version_max) && + (c1->ssl_options == c2->ssl_options) && + (c1->verifypeer == c2->verifypeer) && + (c1->verifyhost == c2->verifyhost) && + (c1->verifystatus == c2->verifystatus) && + blobcmp(c1->cert_blob, c2->cert_blob) && + blobcmp(c1->ca_info_blob, c2->ca_info_blob) && + blobcmp(c1->issuercert_blob, c2->issuercert_blob) && + Curl_safecmp(c1->CApath, c2->CApath) && + 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 + strcasecompare(c1->cipher_list, c2->cipher_list) && + strcasecompare(c1->cipher_list13, c2->cipher_list13) && + strcasecompare(c1->curves, c2->curves) && + strcasecompare(c1->CRLfile, c2->CRLfile) && + strcasecompare(c1->pinned_key, c2->pinned_key)) + return TRUE; + + return FALSE; +} + +bool Curl_ssl_conn_config_match(struct Curl_easy *data, + struct connectdata *candidate, + bool proxy) +{ +#ifndef CURL_DISABLE_PROXY + if(proxy) + return match_ssl_primary_config(data, &data->set.proxy_ssl.primary, + &candidate->proxy_ssl_config); +#else + (void)proxy; +#endif + return match_ssl_primary_config(data, &data->set.ssl.primary, + &candidate->ssl_config); +} + +static bool clone_ssl_primary_config(struct ssl_primary_config *source, + struct ssl_primary_config *dest) +{ + dest->version = source->version; + dest->version_max = source->version_max; + dest->verifypeer = source->verifypeer; + dest->verifyhost = source->verifyhost; + dest->verifystatus = source->verifystatus; + dest->sessionid = source->sessionid; + dest->ssl_options = source->ssl_options; + + CLONE_BLOB(cert_blob); + CLONE_BLOB(ca_info_blob); + CLONE_BLOB(issuercert_blob); + CLONE_STRING(CApath); + CLONE_STRING(CAfile); + CLONE_STRING(issuercert); + CLONE_STRING(clientcert); + CLONE_STRING(cipher_list); + CLONE_STRING(cipher_list13); + CLONE_STRING(pinned_key); + CLONE_STRING(curves); + CLONE_STRING(CRLfile); +#ifdef USE_TLS_SRP + CLONE_STRING(username); + CLONE_STRING(password); +#endif + + return TRUE; +} + +static void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc) +{ + Curl_safefree(sslc->CApath); + Curl_safefree(sslc->CAfile); + Curl_safefree(sslc->issuercert); + Curl_safefree(sslc->clientcert); + Curl_safefree(sslc->cipher_list); + Curl_safefree(sslc->cipher_list13); + Curl_safefree(sslc->pinned_key); + Curl_safefree(sslc->cert_blob); + Curl_safefree(sslc->ca_info_blob); + Curl_safefree(sslc->issuercert_blob); + Curl_safefree(sslc->curves); + Curl_safefree(sslc->CRLfile); +#ifdef USE_TLS_SRP + Curl_safefree(sslc->username); + Curl_safefree(sslc->password); +#endif +} + +CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) +{ + data->set.ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH]; + data->set.ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE]; + data->set.ssl.primary.CRLfile = data->set.str[STRING_SSL_CRLFILE]; + data->set.ssl.primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; + data->set.ssl.primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; + data->set.ssl.primary.cipher_list = + data->set.str[STRING_SSL_CIPHER_LIST]; + data->set.ssl.primary.cipher_list13 = + data->set.str[STRING_SSL_CIPHER13_LIST]; + data->set.ssl.primary.pinned_key = + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT]; + data->set.ssl.primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; + data->set.ssl.primary.curves = data->set.str[STRING_SSL_EC_CURVES]; +#ifdef USE_TLS_SRP + data->set.ssl.primary.username = data->set.str[STRING_TLSAUTH_USERNAME]; + data->set.ssl.primary.password = data->set.str[STRING_TLSAUTH_PASSWORD]; +#endif + data->set.ssl.cert_type = data->set.str[STRING_CERT_TYPE]; + data->set.ssl.key = data->set.str[STRING_KEY]; + data->set.ssl.key_type = data->set.str[STRING_KEY_TYPE]; + data->set.ssl.key_passwd = data->set.str[STRING_KEY_PASSWD]; + data->set.ssl.primary.clientcert = data->set.str[STRING_CERT]; + data->set.ssl.key_blob = data->set.blobs[BLOB_KEY]; + +#ifndef CURL_DISABLE_PROXY + data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; + data->set.proxy_ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; + data->set.proxy_ssl.primary.cipher_list = + data->set.str[STRING_SSL_CIPHER_LIST_PROXY]; + data->set.proxy_ssl.primary.cipher_list13 = + data->set.str[STRING_SSL_CIPHER13_LIST_PROXY]; + data->set.proxy_ssl.primary.pinned_key = + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]; + data->set.proxy_ssl.primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; + data->set.proxy_ssl.primary.ca_info_blob = + data->set.blobs[BLOB_CAINFO_PROXY]; + data->set.proxy_ssl.primary.issuercert = + data->set.str[STRING_SSL_ISSUERCERT_PROXY]; + data->set.proxy_ssl.primary.issuercert_blob = + data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; + data->set.proxy_ssl.primary.CRLfile = + data->set.str[STRING_SSL_CRLFILE_PROXY]; + data->set.proxy_ssl.cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; + data->set.proxy_ssl.key = data->set.str[STRING_KEY_PROXY]; + data->set.proxy_ssl.key_type = data->set.str[STRING_KEY_TYPE_PROXY]; + data->set.proxy_ssl.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; + data->set.proxy_ssl.primary.clientcert = data->set.str[STRING_CERT_PROXY]; + data->set.proxy_ssl.key_blob = data->set.blobs[BLOB_KEY_PROXY]; +#ifdef USE_TLS_SRP + data->set.proxy_ssl.primary.username = + data->set.str[STRING_TLSAUTH_USERNAME_PROXY]; + data->set.proxy_ssl.primary.password = + data->set.str[STRING_TLSAUTH_PASSWORD_PROXY]; +#endif +#endif /* CURL_DISABLE_PROXY */ + + return CURLE_OK; +} + +CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, + struct connectdata *conn) +{ + /* Clone "primary" SSL configurations from the esay handle to + * the connection. They are used for connection cache matching and + * probably outlive the easy handle */ + if(!clone_ssl_primary_config(&data->set.ssl.primary, &conn->ssl_config)) + return CURLE_OUT_OF_MEMORY; +#ifndef CURL_DISABLE_PROXY + if(!clone_ssl_primary_config(&data->set.proxy_ssl.primary, + &conn->proxy_ssl_config)) + return CURLE_OUT_OF_MEMORY; +#endif + return CURLE_OK; +} + +void Curl_ssl_conn_config_cleanup(struct connectdata *conn) +{ + Curl_free_primary_ssl_config(&conn->ssl_config); +#ifndef CURL_DISABLE_PROXY + Curl_free_primary_ssl_config(&conn->proxy_ssl_config); +#endif +} + +void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy) +{ + /* May be called on an easy that has no connection yet */ + if(data->conn) { + struct ssl_primary_config *src, *dest; +#ifndef CURL_DISABLE_PROXY + src = for_proxy? &data->set.proxy_ssl.primary : &data->set.ssl.primary; + dest = for_proxy? &data->conn->proxy_ssl_config : &data->conn->ssl_config; +#else + (void)for_proxy; + src = &data->set.ssl.primary; + dest = &data->conn->ssl_config; +#endif + dest->verifyhost = src->verifyhost; + dest->verifypeer = src->verifypeer; + dest->verifystatus = src->verifystatus; + } +} + +#ifdef USE_SSL +static int multissl_setup(const struct Curl_ssl *backend); +#endif + +curl_sslbackend Curl_ssl_backend(void) +{ +#ifdef USE_SSL + multissl_setup(NULL); + return Curl_ssl->info.id; +#else + return CURLSSLBACKEND_NONE; +#endif +} + +#ifdef USE_SSL + +/* "global" init done? */ +static bool init_ssl = FALSE; + +/** + * Global SSL init + * + * @retval 0 error initializing SSL + * @retval 1 SSL initialized successfully + */ +int Curl_ssl_init(void) +{ + /* make sure this is only done once */ + if(init_ssl) + return 1; + init_ssl = TRUE; /* never again */ + + return Curl_ssl->init(); +} + +#if defined(CURL_WITH_MULTI_SSL) +static const struct Curl_ssl Curl_ssl_multi; +#endif + +/* Global cleanup */ +void Curl_ssl_cleanup(void) +{ + if(init_ssl) { + /* only cleanup if we did a previous init */ + Curl_ssl->cleanup(); +#if defined(CURL_WITH_MULTI_SSL) + Curl_ssl = &Curl_ssl_multi; +#endif + init_ssl = FALSE; + } +} + +static bool ssl_prefs_check(struct Curl_easy *data) +{ + /* check for CURLOPT_SSLVERSION invalid parameter value */ + const unsigned char sslver = data->set.ssl.primary.version; + if(sslver >= CURL_SSLVERSION_LAST) { + failf(data, "Unrecognized parameter value passed via CURLOPT_SSLVERSION"); + return FALSE; + } + + switch(data->set.ssl.primary.version_max) { + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_DEFAULT: + break; + + default: + if((data->set.ssl.primary.version_max >> 16) < sslver) { + failf(data, "CURL_SSLVERSION_MAX incompatible with CURL_SSLVERSION"); + return FALSE; + } + } + + return TRUE; +} + +static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data, + const struct alpn_spec *alpn) +{ + struct ssl_connect_data *ctx; + + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) + return NULL; + + ctx->alpn = alpn; + ctx->backend = calloc(1, Curl_ssl->sizeof_ssl_backend_data); + if(!ctx->backend) { + free(ctx); + return NULL; + } + return ctx; +} + +static void cf_ctx_free(struct ssl_connect_data *ctx) +{ + if(ctx) { + free(ctx->backend); + free(ctx); + } +} + +static CURLcode ssl_connect(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + CURLcode result; + + if(!ssl_prefs_check(data)) + return CURLE_SSL_CONNECT_ERROR; + + /* mark this is being ssl-enabled from here on. */ + connssl->state = ssl_connection_negotiating; + + result = Curl_ssl->connect_blocking(cf, data); + + if(!result) { + DEBUGASSERT(connssl->state == ssl_connection_complete); + } + + return result; +} + +static CURLcode +ssl_connect_nonblocking(struct Curl_cfilter *cf, struct Curl_easy *data, + bool *done) +{ + if(!ssl_prefs_check(data)) + return CURLE_SSL_CONNECT_ERROR; + + /* mark this is being ssl requested from here on. */ + return Curl_ssl->connect_nonblocking(cf, data, done); +} + +/* + * Lock shared SSL session data + */ +void Curl_ssl_sessionid_lock(struct Curl_easy *data) +{ + if(SSLSESSION_SHARED(data)) + Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); +} + +/* + * Unlock shared SSL session data + */ +void Curl_ssl_sessionid_unlock(struct Curl_easy *data) +{ + if(SSLSESSION_SHARED(data)) + Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); +} + +/* + * Check if there's a session ID for the given connection in the cache, and if + * there's one suitable, it is provided. Returns TRUE when no entry matched. + */ +bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, + struct Curl_easy *data, + void **ssl_sessionid, + size_t *idsize) /* set 0 if unknown */ +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct Curl_ssl_session *check; + size_t i; + long *general_age; + bool no_match = TRUE; + + *ssl_sessionid = NULL; + if(!ssl_config) + return TRUE; + + DEBUGASSERT(ssl_config->primary.sessionid); + + if(!ssl_config->primary.sessionid || !data->state.session) + /* session ID reuse is disabled or the session cache has not been + setup */ + return TRUE; + + /* Lock if shared */ + if(SSLSESSION_SHARED(data)) + general_age = &data->share->sessionage; + else + general_age = &data->state.sessionage; + + for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) { + check = &data->state.session[i]; + if(!check->sessionid) + /* not session ID means blank entry */ + continue; + if(strcasecompare(connssl->peer.hostname, check->name) && + ((!cf->conn->bits.conn_to_host && !check->conn_to_host) || + (cf->conn->bits.conn_to_host && check->conn_to_host && + strcasecompare(cf->conn->conn_to_host.name, check->conn_to_host))) && + ((!cf->conn->bits.conn_to_port && check->conn_to_port == -1) || + (cf->conn->bits.conn_to_port && check->conn_to_port != -1 && + cf->conn->conn_to_port == check->conn_to_port)) && + (connssl->port == check->remote_port) && + strcasecompare(cf->conn->handler->scheme, check->scheme) && + match_ssl_primary_config(data, conn_config, &check->ssl_config)) { + /* yes, we have a session ID! */ + (*general_age)++; /* increase general age */ + check->age = *general_age; /* set this as used in this age */ + *ssl_sessionid = check->sessionid; + if(idsize) + *idsize = check->idsize; + no_match = FALSE; + break; + } + } + + DEBUGF(infof(data, "%s Session ID in cache for %s %s://%s:%d", + no_match? "Didn't find": "Found", + Curl_ssl_cf_is_proxy(cf) ? "proxy" : "host", + cf->conn->handler->scheme, connssl->peer.hostname, + connssl->port)); + return no_match; +} + +/* + * Kill a single session ID entry in the cache. + */ +void Curl_ssl_kill_session(struct Curl_ssl_session *session) +{ + if(session->sessionid) { + /* defensive check */ + + /* free the ID the SSL-layer specific way */ + Curl_ssl->session_free(session->sessionid); + + session->sessionid = NULL; + session->age = 0; /* fresh */ + + Curl_free_primary_ssl_config(&session->ssl_config); + + Curl_safefree(session->name); + Curl_safefree(session->conn_to_host); + } +} + +/* + * Delete the given session ID from the cache. + */ +void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid) +{ + size_t i; + + for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) { + struct Curl_ssl_session *check = &data->state.session[i]; + + if(check->sessionid == ssl_sessionid) { + Curl_ssl_kill_session(check); + break; + } + } +} + +/* + * Store session id in the session cache. The ID passed on to this function + * must already have been extracted and allocated the proper way for the SSL + * layer. Curl_XXXX_session_free() will be called to free/kill the session ID + * later on. + */ +CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *ssl_sessionid, + size_t idsize, + bool *added) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + size_t i; + struct Curl_ssl_session *store; + long oldest_age; + char *clone_host; + char *clone_conn_to_host; + int conn_to_port; + long *general_age; + + if(added) + *added = FALSE; + + if(!data->state.session) + return CURLE_OK; + + store = &data->state.session[0]; + oldest_age = data->state.session[0].age; /* zero if unused */ + (void)ssl_config; + DEBUGASSERT(ssl_config->primary.sessionid); + + clone_host = strdup(connssl->peer.hostname); + if(!clone_host) + return CURLE_OUT_OF_MEMORY; /* bail out */ + + if(cf->conn->bits.conn_to_host) { + clone_conn_to_host = strdup(cf->conn->conn_to_host.name); + if(!clone_conn_to_host) { + free(clone_host); + return CURLE_OUT_OF_MEMORY; /* bail out */ + } + } + else + clone_conn_to_host = NULL; + + if(cf->conn->bits.conn_to_port) + conn_to_port = cf->conn->conn_to_port; + else + conn_to_port = -1; + + /* Now we should add the session ID and the host name to the cache, (remove + the oldest if necessary) */ + + /* If using shared SSL session, lock! */ + if(SSLSESSION_SHARED(data)) { + general_age = &data->share->sessionage; + } + else { + general_age = &data->state.sessionage; + } + + /* find an empty slot for us, or find the oldest */ + for(i = 1; (i < data->set.general_ssl.max_ssl_sessions) && + data->state.session[i].sessionid; i++) { + if(data->state.session[i].age < oldest_age) { + oldest_age = data->state.session[i].age; + store = &data->state.session[i]; + } + } + if(i == data->set.general_ssl.max_ssl_sessions) + /* cache is full, we must "kill" the oldest entry! */ + Curl_ssl_kill_session(store); + else + store = &data->state.session[i]; /* use this slot */ + + /* now init the session struct wisely */ + store->sessionid = ssl_sessionid; + store->idsize = idsize; + store->age = *general_age; /* set current age */ + /* free it if there's one already present */ + free(store->name); + free(store->conn_to_host); + store->name = clone_host; /* clone host name */ + store->conn_to_host = clone_conn_to_host; /* clone connect to host name */ + store->conn_to_port = conn_to_port; /* connect to port number */ + /* port number */ + store->remote_port = connssl->port; + store->scheme = cf->conn->handler->scheme; + + if(!clone_ssl_primary_config(conn_config, &store->ssl_config)) { + Curl_free_primary_ssl_config(&store->ssl_config); + store->sessionid = NULL; /* let caller free sessionid */ + free(clone_host); + free(clone_conn_to_host); + return CURLE_OUT_OF_MEMORY; + } + + if(added) + *added = TRUE; + + DEBUGF(infof(data, "Added Session ID to cache for %s://%s:%d [%s]", + store->scheme, store->name, store->remote_port, + Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server")); + return CURLE_OK; +} + +void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend) +{ + if(Curl_ssl->free_multi_ssl_backend_data && mbackend) + Curl_ssl->free_multi_ssl_backend_data(mbackend); +} + +void Curl_ssl_close_all(struct Curl_easy *data) +{ + /* kill the session ID cache if not shared */ + if(data->state.session && !SSLSESSION_SHARED(data)) { + size_t i; + for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) + /* the single-killer function handles empty table slots */ + Curl_ssl_kill_session(&data->state.session[i]); + + /* free the cache data */ + Curl_safefree(data->state.session); + } + + Curl_ssl->close_all(data); +} + +void Curl_ssl_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, + struct easy_pollset *ps) +{ + if(!cf->connected) { + struct ssl_connect_data *connssl = cf->ctx; + curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); + if(sock != CURL_SOCKET_BAD) { + if(connssl->connecting_state == ssl_connect_2_writing) { + Curl_pollset_set_out_only(data, ps, sock); + } + else { + Curl_pollset_set_in_only(data, ps, sock); + } + } + } +} + +/* Selects an SSL crypto engine + */ +CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine) +{ + return Curl_ssl->set_engine(data, engine); +} + +/* Selects the default SSL crypto engine + */ +CURLcode Curl_ssl_set_engine_default(struct Curl_easy *data) +{ + return Curl_ssl->set_engine_default(data); +} + +/* Return list of OpenSSL crypto engine names. */ +struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data) +{ + return Curl_ssl->engines_list(data); +} + +/* + * This sets up a session ID cache to the specified size. Make sure this code + * is agnostic to what underlying SSL technology we use. + */ +CURLcode Curl_ssl_initsessions(struct Curl_easy *data, size_t amount) +{ + struct Curl_ssl_session *session; + + if(data->state.session) + /* this is just a precaution to prevent multiple inits */ + return CURLE_OK; + + session = calloc(amount, sizeof(struct Curl_ssl_session)); + if(!session) + return CURLE_OUT_OF_MEMORY; + + /* store the info in the SSL section */ + data->set.general_ssl.max_ssl_sessions = amount; + data->state.session = session; + data->state.sessionage = 1; /* this is brand new */ + return CURLE_OK; +} + +static size_t multissl_version(char *buffer, size_t size); + +void Curl_ssl_version(char *buffer, size_t size) +{ +#ifdef CURL_WITH_MULTI_SSL + (void)multissl_version(buffer, size); +#else + (void)Curl_ssl->version(buffer, size); +#endif +} + +void Curl_ssl_free_certinfo(struct Curl_easy *data) +{ + struct curl_certinfo *ci = &data->info.certs; + + if(ci->num_of_certs) { + /* free all individual lists used */ + int i; + for(i = 0; i<ci->num_of_certs; i++) { + curl_slist_free_all(ci->certinfo[i]); + ci->certinfo[i] = NULL; + } + + free(ci->certinfo); /* free the actual array too */ + ci->certinfo = NULL; + ci->num_of_certs = 0; + } +} + +CURLcode Curl_ssl_init_certinfo(struct Curl_easy *data, int num) +{ + struct curl_certinfo *ci = &data->info.certs; + struct curl_slist **table; + + /* Free any previous certificate information structures */ + Curl_ssl_free_certinfo(data); + + /* Allocate the required certificate information structures */ + table = calloc((size_t) num, sizeof(struct curl_slist *)); + if(!table) + return CURLE_OUT_OF_MEMORY; + + ci->num_of_certs = num; + ci->certinfo = table; + + return CURLE_OK; +} + +/* + * 'value' is NOT a null-terminated string + */ +CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, + int certnum, + const char *label, + const char *value, + size_t valuelen) +{ + struct curl_certinfo *ci = &data->info.certs; + char *output; + struct curl_slist *nl; + CURLcode result = CURLE_OK; + size_t labellen = strlen(label); + size_t outlen = labellen + 1 + valuelen + 1; /* label:value\0 */ + + output = malloc(outlen); + if(!output) + return CURLE_OUT_OF_MEMORY; + + /* sprintf the label and colon */ + msnprintf(output, outlen, "%s:", label); + + /* memcpy the value (it might not be null-terminated) */ + memcpy(&output[labellen + 1], value, valuelen); + + /* null-terminate the output */ + output[labellen + 1 + valuelen] = 0; + + nl = Curl_slist_append_nodup(ci->certinfo[certnum], output); + if(!nl) { + free(output); + curl_slist_free_all(ci->certinfo[certnum]); + result = CURLE_OUT_OF_MEMORY; + } + + ci->certinfo[certnum] = nl; + return result; +} + +CURLcode Curl_ssl_random(struct Curl_easy *data, + unsigned char *entropy, + size_t length) +{ + return Curl_ssl->random(data, entropy, length); +} + +/* + * Public key pem to der conversion + */ + +static CURLcode pubkey_pem_to_der(const char *pem, + unsigned char **der, size_t *der_len) +{ + char *stripped_pem, *begin_pos, *end_pos; + size_t pem_count, stripped_pem_count = 0, pem_len; + CURLcode result; + + /* if no pem, exit. */ + if(!pem) + return CURLE_BAD_CONTENT_ENCODING; + + begin_pos = strstr(pem, "-----BEGIN PUBLIC KEY-----"); + if(!begin_pos) + return CURLE_BAD_CONTENT_ENCODING; + + pem_count = begin_pos - pem; + /* Invalid if not at beginning AND not directly following \n */ + if(0 != pem_count && '\n' != pem[pem_count - 1]) + return CURLE_BAD_CONTENT_ENCODING; + + /* 26 is length of "-----BEGIN PUBLIC KEY-----" */ + pem_count += 26; + + /* Invalid if not directly following \n */ + end_pos = strstr(pem + pem_count, "\n-----END PUBLIC KEY-----"); + if(!end_pos) + return CURLE_BAD_CONTENT_ENCODING; + + pem_len = end_pos - pem; + + stripped_pem = malloc(pem_len - pem_count + 1); + if(!stripped_pem) + return CURLE_OUT_OF_MEMORY; + + /* + * Here we loop through the pem array one character at a time between the + * correct indices, and place each character that is not '\n' or '\r' + * into the stripped_pem array, which should represent the raw base64 string + */ + while(pem_count < pem_len) { + if('\n' != pem[pem_count] && '\r' != pem[pem_count]) + stripped_pem[stripped_pem_count++] = pem[pem_count]; + ++pem_count; + } + /* Place the null terminator in the correct place */ + stripped_pem[stripped_pem_count] = '\0'; + + result = Curl_base64_decode(stripped_pem, der, der_len); + + Curl_safefree(stripped_pem); + + return result; +} + +/* + * Generic pinned public key check. + */ + +CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, + const char *pinnedpubkey, + const unsigned char *pubkey, size_t pubkeylen) +{ + FILE *fp; + unsigned char *buf = NULL, *pem_ptr = NULL; + CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)data; +#endif + + /* if a path wasn't specified, don't pin */ + if(!pinnedpubkey) + return CURLE_OK; + if(!pubkey || !pubkeylen) + return result; + + /* only do this if pinnedpubkey starts with "sha256//", length 8 */ + if(strncmp(pinnedpubkey, "sha256//", 8) == 0) { + CURLcode encode; + size_t encodedlen = 0, pinkeylen; + char *encoded = NULL, *pinkeycopy, *begin_pos, *end_pos; + unsigned char *sha256sumdigest; + + if(!Curl_ssl->sha256sum) { + /* without sha256 support, this cannot match */ + return result; + } + + /* compute sha256sum of public key */ + sha256sumdigest = malloc(CURL_SHA256_DIGEST_LENGTH); + if(!sha256sumdigest) + return CURLE_OUT_OF_MEMORY; + encode = Curl_ssl->sha256sum(pubkey, pubkeylen, + sha256sumdigest, CURL_SHA256_DIGEST_LENGTH); + + if(!encode) + encode = Curl_base64_encode((char *)sha256sumdigest, + CURL_SHA256_DIGEST_LENGTH, &encoded, + &encodedlen); + Curl_safefree(sha256sumdigest); + + if(encode) + return encode; + + infof(data, " public key hash: sha256//%s", encoded); + + /* it starts with sha256//, copy so we can modify it */ + pinkeylen = strlen(pinnedpubkey) + 1; + pinkeycopy = malloc(pinkeylen); + if(!pinkeycopy) { + Curl_safefree(encoded); + return CURLE_OUT_OF_MEMORY; + } + memcpy(pinkeycopy, pinnedpubkey, pinkeylen); + /* point begin_pos to the copy, and start extracting keys */ + begin_pos = pinkeycopy; + do { + end_pos = strstr(begin_pos, ";sha256//"); + /* + * if there is an end_pos, null terminate, + * otherwise it'll go to the end of the original string + */ + if(end_pos) + end_pos[0] = '\0'; + + /* compare base64 sha256 digests, 8 is the length of "sha256//" */ + if(encodedlen == strlen(begin_pos + 8) && + !memcmp(encoded, begin_pos + 8, encodedlen)) { + result = CURLE_OK; + break; + } + + /* + * change back the null-terminator we changed earlier, + * and look for next begin + */ + if(end_pos) { + end_pos[0] = ';'; + begin_pos = strstr(end_pos, "sha256//"); + } + } while(end_pos && begin_pos); + Curl_safefree(encoded); + Curl_safefree(pinkeycopy); + return result; + } + + fp = fopen(pinnedpubkey, "rb"); + if(!fp) + return result; + + do { + long filesize; + size_t size, pem_len; + CURLcode pem_read; + + /* Determine the file's size */ + if(fseek(fp, 0, SEEK_END)) + break; + filesize = ftell(fp); + if(fseek(fp, 0, SEEK_SET)) + break; + if(filesize < 0 || filesize > MAX_PINNED_PUBKEY_SIZE) + break; + + /* + * if the size of our certificate is bigger than the file + * size then it can't match + */ + size = curlx_sotouz((curl_off_t) filesize); + if(pubkeylen > size) + break; + + /* + * Allocate buffer for the pinned key + * With 1 additional byte for null terminator in case of PEM key + */ + buf = malloc(size + 1); + if(!buf) + break; + + /* Returns number of elements read, which should be 1 */ + if((int) fread(buf, size, 1, fp) != 1) + break; + + /* If the sizes are the same, it can't be base64 encoded, must be der */ + if(pubkeylen == size) { + if(!memcmp(pubkey, buf, pubkeylen)) + result = CURLE_OK; + break; + } + + /* + * Otherwise we will assume it's PEM and try to decode it + * after placing null terminator + */ + buf[size] = '\0'; + pem_read = pubkey_pem_to_der((const char *)buf, &pem_ptr, &pem_len); + /* if it wasn't read successfully, exit */ + if(pem_read) + break; + + /* + * if the size of our certificate doesn't match the size of + * the decoded file, they can't be the same, otherwise compare + */ + if(pubkeylen == pem_len && !memcmp(pubkey, pem_ptr, pubkeylen)) + result = CURLE_OK; + } while(0); + + Curl_safefree(buf); + Curl_safefree(pem_ptr); + fclose(fp); + + return result; +} + +/* + * Check whether the SSL backend supports the status_request extension. + */ +bool Curl_ssl_cert_status_request(void) +{ + return Curl_ssl->cert_status_request(); +} + +/* + * Check whether the SSL backend supports false start. + */ +bool Curl_ssl_false_start(struct Curl_easy *data) +{ + (void)data; + return Curl_ssl->false_start(); +} + +/* + * Default implementations for unsupported functions. + */ + +int Curl_none_init(void) +{ + return 1; +} + +void Curl_none_cleanup(void) +{ } + +int Curl_none_shutdown(struct Curl_cfilter *cf UNUSED_PARAM, + struct Curl_easy *data UNUSED_PARAM) +{ + (void)data; + (void)cf; + return 0; +} + +int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + (void)cf; + (void)data; + return -1; +} + +CURLcode Curl_none_random(struct Curl_easy *data UNUSED_PARAM, + unsigned char *entropy UNUSED_PARAM, + size_t length UNUSED_PARAM) +{ + (void)data; + (void)entropy; + (void)length; + return CURLE_NOT_BUILT_IN; +} + +void Curl_none_close_all(struct Curl_easy *data UNUSED_PARAM) +{ + (void)data; +} + +void Curl_none_session_free(void *ptr UNUSED_PARAM) +{ + (void)ptr; +} + +bool Curl_none_data_pending(struct Curl_cfilter *cf UNUSED_PARAM, + const struct Curl_easy *data UNUSED_PARAM) +{ + (void)cf; + (void)data; + return 0; +} + +bool Curl_none_cert_status_request(void) +{ + return FALSE; +} + +CURLcode Curl_none_set_engine(struct Curl_easy *data UNUSED_PARAM, + const char *engine UNUSED_PARAM) +{ + (void)data; + (void)engine; + return CURLE_NOT_BUILT_IN; +} + +CURLcode Curl_none_set_engine_default(struct Curl_easy *data UNUSED_PARAM) +{ + (void)data; + return CURLE_NOT_BUILT_IN; +} + +struct curl_slist *Curl_none_engines_list(struct Curl_easy *data UNUSED_PARAM) +{ + (void)data; + return (struct curl_slist *)NULL; +} + +bool Curl_none_false_start(void) +{ + return FALSE; +} + +static int multissl_init(void) +{ + if(multissl_setup(NULL)) + return 1; + return Curl_ssl->init(); +} + +static CURLcode multissl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + if(multissl_setup(NULL)) + return CURLE_FAILED_INIT; + return Curl_ssl->connect_blocking(cf, data); +} + +static CURLcode multissl_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + if(multissl_setup(NULL)) + return CURLE_FAILED_INIT; + return Curl_ssl->connect_nonblocking(cf, data, done); +} + +static void multissl_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + if(multissl_setup(NULL)) + return; + Curl_ssl->adjust_pollset(cf, data, ps); +} + +static void *multissl_get_internals(struct ssl_connect_data *connssl, + CURLINFO info) +{ + if(multissl_setup(NULL)) + return NULL; + return Curl_ssl->get_internals(connssl, info); +} + +static void multissl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + if(multissl_setup(NULL)) + return; + Curl_ssl->close(cf, data); +} + +static ssize_t multissl_recv_plain(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t len, CURLcode *code) +{ + if(multissl_setup(NULL)) + return CURLE_FAILED_INIT; + return Curl_ssl->recv_plain(cf, data, buf, len, code); +} + +static ssize_t multissl_send_plain(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, size_t len, + CURLcode *code) +{ + if(multissl_setup(NULL)) + return CURLE_FAILED_INIT; + return Curl_ssl->send_plain(cf, data, mem, len, code); +} + +static const struct Curl_ssl Curl_ssl_multi = { + { CURLSSLBACKEND_NONE, "multi" }, /* info */ + 0, /* supports nothing */ + (size_t)-1, /* something insanely large to be on the safe side */ + + multissl_init, /* init */ + Curl_none_cleanup, /* cleanup */ + multissl_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + Curl_none_shutdown, /* shutdown */ + Curl_none_data_pending, /* data_pending */ + Curl_none_random, /* random */ + Curl_none_cert_status_request, /* cert_status_request */ + multissl_connect, /* connect */ + multissl_connect_nonblocking, /* connect_nonblocking */ + multissl_adjust_pollset, /* adjust_pollset */ + multissl_get_internals, /* get_internals */ + multissl_close, /* close_one */ + Curl_none_close_all, /* close_all */ + Curl_none_session_free, /* session_free */ + Curl_none_set_engine, /* set_engine */ + Curl_none_set_engine_default, /* set_engine_default */ + Curl_none_engines_list, /* engines_list */ + Curl_none_false_start, /* false_start */ + NULL, /* sha256sum */ + NULL, /* associate_connection */ + NULL, /* disassociate_connection */ + NULL, /* free_multi_ssl_backend_data */ + multissl_recv_plain, /* recv decrypted data */ + multissl_send_plain, /* send data to encrypt */ +}; + +const struct Curl_ssl *Curl_ssl = +#if defined(CURL_WITH_MULTI_SSL) + &Curl_ssl_multi; +#elif defined(USE_WOLFSSL) + &Curl_ssl_wolfssl; +#elif defined(USE_SECTRANSP) + &Curl_ssl_sectransp; +#elif defined(USE_GNUTLS) + &Curl_ssl_gnutls; +#elif defined(USE_MBEDTLS) + &Curl_ssl_mbedtls; +#elif defined(USE_RUSTLS) + &Curl_ssl_rustls; +#elif defined(USE_OPENSSL) + &Curl_ssl_openssl; +#elif defined(USE_SCHANNEL) + &Curl_ssl_schannel; +#elif defined(USE_BEARSSL) + &Curl_ssl_bearssl; +#else +#error "Missing struct Curl_ssl for selected SSL backend" +#endif + +static const struct Curl_ssl *available_backends[] = { +#if defined(USE_WOLFSSL) + &Curl_ssl_wolfssl, +#endif +#if defined(USE_SECTRANSP) + &Curl_ssl_sectransp, +#endif +#if defined(USE_GNUTLS) + &Curl_ssl_gnutls, +#endif +#if defined(USE_MBEDTLS) + &Curl_ssl_mbedtls, +#endif +#if defined(USE_OPENSSL) + &Curl_ssl_openssl, +#endif +#if defined(USE_SCHANNEL) + &Curl_ssl_schannel, +#endif +#if defined(USE_BEARSSL) + &Curl_ssl_bearssl, +#endif +#if defined(USE_RUSTLS) + &Curl_ssl_rustls, +#endif + NULL +}; + +static size_t multissl_version(char *buffer, size_t size) +{ + static const struct Curl_ssl *selected; + static char backends[200]; + static size_t backends_len; + const struct Curl_ssl *current; + + current = Curl_ssl == &Curl_ssl_multi ? available_backends[0] : Curl_ssl; + + if(current != selected) { + char *p = backends; + char *end = backends + sizeof(backends); + int i; + + selected = current; + + backends[0] = '\0'; + + for(i = 0; available_backends[i]; ++i) { + char vb[200]; + bool paren = (selected != available_backends[i]); + + if(available_backends[i]->version(vb, sizeof(vb))) { + p += msnprintf(p, end - p, "%s%s%s%s", (p != backends ? " " : ""), + (paren ? "(" : ""), vb, (paren ? ")" : "")); + } + } + + backends_len = p - backends; + } + + if(!size) + return 0; + + if(size <= backends_len) { + strncpy(buffer, backends, size - 1); + buffer[size - 1] = '\0'; + return size - 1; + } + + strcpy(buffer, backends); + return backends_len; +} + +static int multissl_setup(const struct Curl_ssl *backend) +{ + const char *env; + char *env_tmp; + + if(Curl_ssl != &Curl_ssl_multi) + return 1; + + if(backend) { + Curl_ssl = backend; + return 0; + } + + if(!available_backends[0]) + return 1; + + env = env_tmp = curl_getenv("CURL_SSL_BACKEND"); +#ifdef CURL_DEFAULT_SSL_BACKEND + if(!env) + env = CURL_DEFAULT_SSL_BACKEND; +#endif + if(env) { + int i; + for(i = 0; available_backends[i]; i++) { + if(strcasecompare(env, available_backends[i]->info.name)) { + Curl_ssl = available_backends[i]; + free(env_tmp); + return 0; + } + } + } + + /* Fall back to first available backend */ + Curl_ssl = available_backends[0]; + free(env_tmp); + return 0; +} + +/* This function is used to select the SSL backend to use. It is called by + curl_global_sslset (easy.c) which uses the global init lock. */ +CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, + const curl_ssl_backend ***avail) +{ + int i; + + if(avail) + *avail = (const curl_ssl_backend **)&available_backends; + + if(Curl_ssl != &Curl_ssl_multi) + return id == Curl_ssl->info.id || + (name && strcasecompare(name, Curl_ssl->info.name)) ? + CURLSSLSET_OK : +#if defined(CURL_WITH_MULTI_SSL) + CURLSSLSET_TOO_LATE; +#else + CURLSSLSET_UNKNOWN_BACKEND; +#endif + + for(i = 0; available_backends[i]; i++) { + if(available_backends[i]->info.id == id || + (name && strcasecompare(available_backends[i]->info.name, name))) { + multissl_setup(available_backends[i]); + return CURLSSLSET_OK; + } + } + + return CURLSSLSET_UNKNOWN_BACKEND; +} + +#else /* USE_SSL */ +CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, + const curl_ssl_backend ***avail) +{ + (void)id; + (void)name; + (void)avail; + return CURLSSLSET_NO_BACKENDS; +} + +#endif /* !USE_SSL */ + +#ifdef USE_SSL + +void Curl_ssl_peer_cleanup(struct ssl_peer *peer) +{ + if(peer->dispname != peer->hostname) + free(peer->dispname); + free(peer->sni); + free(peer->hostname); + peer->hostname = peer->sni = peer->dispname = NULL; + peer->is_ip_address = FALSE; +} + +static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + if(connssl) { + Curl_ssl->close(cf, data); + connssl->state = ssl_connection_none; + Curl_ssl_peer_cleanup(&connssl->peer); + } + cf->connected = FALSE; +} + +static int is_ip_address(const char *hostname) +{ +#ifdef ENABLE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + return (hostname && hostname[0] && (Curl_inet_pton(AF_INET, hostname, &addr) +#ifdef ENABLE_IPV6 + || Curl_inet_pton(AF_INET6, hostname, &addr) +#endif + )); +} + +CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, struct Curl_cfilter *cf) +{ + struct ssl_connect_data *connssl = cf->ctx; + const char *ehostname, *edispname; + int eport; + + /* We need the hostname for SNI negotiation. Once handshaked, this + * remains the SNI hostname for the TLS connection. But when the + * connection is reused, the settings in cf->conn might change. + * So we keep a copy of the hostname we use for SNI. + */ +#ifndef CURL_DISABLE_PROXY + if(Curl_ssl_cf_is_proxy(cf)) { + ehostname = cf->conn->http_proxy.host.name; + edispname = cf->conn->http_proxy.host.dispname; + eport = cf->conn->http_proxy.port; + } + else +#endif + { + ehostname = cf->conn->host.name; + edispname = cf->conn->host.dispname; + eport = cf->conn->remote_port; + } + + /* change if ehostname changed */ + if(ehostname && (!peer->hostname + || strcmp(ehostname, peer->hostname))) { + Curl_ssl_peer_cleanup(peer); + peer->hostname = strdup(ehostname); + if(!peer->hostname) { + Curl_ssl_peer_cleanup(peer); + return CURLE_OUT_OF_MEMORY; + } + if(!edispname || !strcmp(ehostname, edispname)) + peer->dispname = peer->hostname; + else { + peer->dispname = strdup(edispname); + if(!peer->dispname) { + Curl_ssl_peer_cleanup(peer); + return CURLE_OUT_OF_MEMORY; + } + } + + peer->sni = NULL; + peer->is_ip_address = is_ip_address(peer->hostname)? TRUE : FALSE; + if(peer->hostname[0] && !peer->is_ip_address) { + /* not an IP address, normalize according to RCC 6066 ch. 3, + * max len of SNI is 2^16-1, no trailing dot */ + size_t len = strlen(peer->hostname); + if(len && (peer->hostname[len-1] == '.')) + len--; + if(len < USHRT_MAX) { + peer->sni = calloc(1, len + 1); + if(!peer->sni) { + Curl_ssl_peer_cleanup(peer); + return CURLE_OUT_OF_MEMORY; + } + Curl_strntolower(peer->sni, peer->hostname, len); + peer->sni[len] = 0; + } + } + + } + connssl->port = eport; + return CURLE_OK; +} + +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; +} + +static void ssl_cf_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + cf_close(cf, data); + if(cf->next) + cf->next->cft->do_close(cf->next, data); + CF_DATA_RESTORE(cf, save); +} + +static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct cf_call_data save; + CURLcode result; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + CURL_TRC_CF(data, cf, "cf_connect()"); + (void)connssl; + DEBUGASSERT(data->conn); + DEBUGASSERT(data->conn == cf->conn); + DEBUGASSERT(connssl); + DEBUGASSERT(cf->conn->host.name); + + result = cf->next->cft->do_connect(cf->next, data, blocking, done); + if(result || !*done) + goto out; + + *done = FALSE; + result = Curl_ssl_peer_init(&connssl->peer, cf); + if(result) + goto out; + + if(blocking) { + result = ssl_connect(cf, data); + *done = (result == CURLE_OK); + } + else { + result = ssl_connect_nonblocking(cf, data, done); + } + + if(!result && *done) { + cf->connected = TRUE; + connssl->handshake_done = Curl_now(); + DEBUGASSERT(connssl->state == ssl_connection_complete); + } +out: + CURL_TRC_CF(data, cf, "cf_connect() -> %d, done=%d", result, *done); + CF_DATA_RESTORE(cf, save); + return result; +} + +static bool ssl_cf_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_call_data save; + bool result; + + CF_DATA_SAVE(save, cf, data); + if(Curl_ssl->data_pending(cf, data)) + result = TRUE; + else + result = cf->next->cft->has_data_pending(cf->next, data); + CF_DATA_RESTORE(cf, save); + return result; +} + +static ssize_t ssl_cf_send(struct Curl_cfilter *cf, + struct Curl_easy *data, const void *buf, size_t len, + CURLcode *err) +{ + struct cf_call_data save; + ssize_t nwritten; + + CF_DATA_SAVE(save, cf, data); + *err = CURLE_OK; + nwritten = Curl_ssl->send_plain(cf, data, buf, len, err); + CF_DATA_RESTORE(cf, save); + return nwritten; +} + +static ssize_t ssl_cf_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, char *buf, size_t len, + CURLcode *err) +{ + struct cf_call_data save; + ssize_t nread; + + CF_DATA_SAVE(save, cf, data); + *err = CURLE_OK; + nread = Curl_ssl->recv_plain(cf, data, buf, len, err); + if(nread > 0) { + DEBUGASSERT((size_t)nread <= len); + } + else if(nread == 0) { + /* eof */ + *err = CURLE_OK; + } + CURL_TRC_CF(data, cf, "cf_recv(len=%zu) -> %zd, %d", len, nread, *err); + CF_DATA_RESTORE(cf, save); + return nread; +} + +static void ssl_cf_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_call_data save; + + if(!cf->connected) { + CF_DATA_SAVE(save, cf, data); + Curl_ssl->adjust_pollset(cf, data, ps); + CF_DATA_RESTORE(cf, save); + } +} + +static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_call_data save; + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_DATA_ATTACH: + if(Curl_ssl->attach_data) { + CF_DATA_SAVE(save, cf, data); + Curl_ssl->attach_data(cf, data); + CF_DATA_RESTORE(cf, save); + } + break; + case CF_CTRL_DATA_DETACH: + if(Curl_ssl->detach_data) { + CF_DATA_SAVE(save, cf, data); + Curl_ssl->detach_data(cf, data); + CF_DATA_RESTORE(cf, save); + } + break; + default: + break; + } + return CURLE_OK; +} + +static CURLcode ssl_cf_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + 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; + } + default: + break; + } + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +static bool cf_ssl_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, + bool *input_pending) +{ + struct cf_call_data save; + int result; + /* + * This function tries to determine connection status. + * + * Return codes: + * 1 means the connection is still in place + * 0 means the connection has been closed + * -1 means the connection status is unknown + */ + CF_DATA_SAVE(save, cf, data); + result = Curl_ssl->check_cxn(cf, data); + CF_DATA_RESTORE(cf, save); + if(result > 0) { + *input_pending = TRUE; + return TRUE; + } + if(result == 0) { + *input_pending = FALSE; + return FALSE; + } + /* ssl backend does not know */ + return cf->next? + cf->next->cft->is_alive(cf->next, data, input_pending) : + FALSE; /* pessimistic in absence of data */ +} + +struct Curl_cftype Curl_cft_ssl = { + "SSL", + CF_TYPE_SSL, + CURL_LOG_LVL_NONE, + ssl_cf_destroy, + ssl_cf_connect, + ssl_cf_close, + Curl_cf_def_get_host, + ssl_cf_adjust_pollset, + ssl_cf_data_pending, + ssl_cf_send, + ssl_cf_recv, + ssl_cf_cntrl, + cf_ssl_is_alive, + Curl_cf_def_conn_keep_alive, + ssl_cf_query, +}; + +struct Curl_cftype Curl_cft_ssl_proxy = { + "SSL-PROXY", + CF_TYPE_SSL, + CURL_LOG_LVL_NONE, + ssl_cf_destroy, + ssl_cf_connect, + ssl_cf_close, + Curl_cf_def_get_host, + ssl_cf_adjust_pollset, + ssl_cf_data_pending, + ssl_cf_send, + ssl_cf_recv, + ssl_cf_cntrl, + cf_ssl_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn) +{ + struct Curl_cfilter *cf = NULL; + struct ssl_connect_data *ctx; + CURLcode result; + + DEBUGASSERT(data->conn); + + ctx = cf_ctx_new(data, alpn_get_spec(data->state.httpwant, + conn->bits.tls_enable_alpn)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = Curl_cf_create(&cf, &Curl_cft_ssl, ctx); + +out: + if(result) + cf_ctx_free(ctx); + *pcf = result? NULL : cf; + return result; +} + +CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf; + CURLcode result; + + result = cf_ssl_create(&cf, data, conn); + if(!result) + Curl_conn_cf_add(data, conn, sockindex, cf); + return result; +} + +CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + CURLcode result; + + result = cf_ssl_create(&cf, data, cf_at->conn); + if(!result) + Curl_conn_cf_insert_after(cf_at, cf); + return result; +} + +#ifndef CURL_DISABLE_PROXY + +static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn) +{ + struct Curl_cfilter *cf = NULL; + struct ssl_connect_data *ctx; + CURLcode result; + bool use_alpn = conn->bits.tls_enable_alpn; + int httpwant = CURL_HTTP_VERSION_1_1; + +#ifdef USE_HTTP2 + if(conn->http_proxy.proxytype == CURLPROXY_HTTPS2) { + use_alpn = TRUE; + httpwant = CURL_HTTP_VERSION_2; + } +#endif + + ctx = cf_ctx_new(data, alpn_get_spec(httpwant, use_alpn)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + result = Curl_cf_create(&cf, &Curl_cft_ssl_proxy, ctx); + +out: + if(result) + cf_ctx_free(ctx); + *pcf = result? NULL : cf; + return result; +} + +CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + CURLcode result; + + result = cf_ssl_proxy_create(&cf, data, cf_at->conn); + if(!result) + Curl_conn_cf_insert_after(cf_at, cf); + return result; +} + +#endif /* !CURL_DISABLE_PROXY */ + +bool Curl_ssl_supports(struct Curl_easy *data, int option) +{ + (void)data; + return (Curl_ssl->supports & option)? TRUE : FALSE; +} + +static struct Curl_cfilter *get_ssl_filter(struct Curl_cfilter *cf) +{ + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy) + return cf; + } + return NULL; +} + + +void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, + CURLINFO info, int n) +{ + void *result = NULL; + (void)n; + if(data->conn) { + struct Curl_cfilter *cf; + /* get first SSL filter in chain, if any is present */ + cf = get_ssl_filter(data->conn->cfilter[sockindex]); + if(cf) { + struct cf_call_data save; + CF_DATA_SAVE(save, cf, data); + result = Curl_ssl->get_internals(cf->ctx, info); + CF_DATA_RESTORE(cf, save); + } + } + return result; +} + +CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, + int sockindex) +{ + struct Curl_cfilter *cf, *head; + CURLcode result = CURLE_OK; + + (void)data; + head = data->conn? data->conn->cfilter[sockindex] : NULL; + for(cf = head; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_ssl) { + if(Curl_ssl->shut_down(cf, data)) + result = CURLE_SSL_SHUTDOWN_FAILED; + Curl_conn_cf_discard_sub(head, cf, data, FALSE); + break; + } + } + return result; +} + +bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf) +{ + return (cf->cft == &Curl_cft_ssl_proxy); +} + +struct ssl_config_data * +Curl_ssl_cf_get_config(struct Curl_cfilter *cf, struct Curl_easy *data) +{ +#ifdef CURL_DISABLE_PROXY + (void)cf; + return &data->set.ssl; +#else + return Curl_ssl_cf_is_proxy(cf)? &data->set.proxy_ssl : &data->set.ssl; +#endif +} + +struct ssl_primary_config * +Curl_ssl_cf_get_primary_config(struct Curl_cfilter *cf) +{ +#ifdef CURL_DISABLE_PROXY + return &cf->conn->ssl_config; +#else + return Curl_ssl_cf_is_proxy(cf)? + &cf->conn->proxy_ssl_config : &cf->conn->ssl_config; +#endif +} + +CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf, + const struct alpn_spec *spec) +{ + size_t i, len; + int off = 0; + unsigned char blen; + + memset(buf, 0, sizeof(*buf)); + for(i = 0; spec && i < spec->count; ++i) { + len = strlen(spec->entries[i]); + if(len >= ALPN_NAME_MAX) + return CURLE_FAILED_INIT; + blen = (unsigned char)len; + if(off + blen + 1 >= (int)sizeof(buf->data)) + return CURLE_FAILED_INIT; + buf->data[off++] = blen; + memcpy(buf->data + off, spec->entries[i], blen); + off += blen; + } + buf->len = off; + return CURLE_OK; +} + +CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf, + const struct alpn_spec *spec) +{ + size_t i, len; + size_t off = 0; + + memset(buf, 0, sizeof(*buf)); + for(i = 0; spec && i < spec->count; ++i) { + len = strlen(spec->entries[i]); + if(len >= ALPN_NAME_MAX) + return CURLE_FAILED_INIT; + if(off + len + 2 >= sizeof(buf->data)) + return CURLE_FAILED_INIT; + if(off) + buf->data[off++] = ','; + memcpy(buf->data + off, spec->entries[i], len); + off += len; + } + buf->data[off] = '\0'; + buf->len = (int)off; + return CURLE_OK; +} + +CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, + struct Curl_easy *data, + const unsigned char *proto, + size_t proto_len) +{ + int can_multi = 0; + unsigned char *palpn = +#ifndef CURL_DISABLE_PROXY + (cf->conn->bits.tunnel_proxy && Curl_ssl_cf_is_proxy(cf))? + &cf->conn->proxy_alpn : &cf->conn->alpn +#else + &cf->conn->alpn +#endif + ; + + if(proto && proto_len) { + if(proto_len == ALPN_HTTP_1_1_LENGTH && + !memcmp(ALPN_HTTP_1_1, proto, ALPN_HTTP_1_1_LENGTH)) { + *palpn = CURL_HTTP_VERSION_1_1; + } +#ifdef USE_HTTP2 + else if(proto_len == ALPN_H2_LENGTH && + !memcmp(ALPN_H2, proto, ALPN_H2_LENGTH)) { + *palpn = CURL_HTTP_VERSION_2; + can_multi = 1; + } +#endif +#ifdef USE_HTTP3 + else if(proto_len == ALPN_H3_LENGTH && + !memcmp(ALPN_H3, proto, ALPN_H3_LENGTH)) { + *palpn = CURL_HTTP_VERSION_3; + can_multi = 1; + } +#endif + else { + *palpn = CURL_HTTP_VERSION_NONE; + failf(data, "unsupported ALPN protocol: '%.*s'", (int)proto_len, proto); + /* TODO: do we want to fail this? Previous code just ignored it and + * some vtls backends even ignore the return code of this function. */ + /* return CURLE_NOT_BUILT_IN; */ + goto out; + } + infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, (int)proto_len, proto); + } + else { + *palpn = CURL_HTTP_VERSION_NONE; + infof(data, VTLS_INFOF_NO_ALPN); + } + +out: + if(!Curl_ssl_cf_is_proxy(cf)) + Curl_multiuse_state(data, can_multi? + BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + return CURLE_OK; +} + +#endif /* USE_SSL */ diff --git a/Utilities/cmcurl/lib/vtls/vtls.h b/Utilities/cmcurl/lib/vtls/vtls.h new file mode 100644 index 0000000..f1856bd --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/vtls.h @@ -0,0 +1,256 @@ +#ifndef HEADER_CURL_VTLS_H +#define HEADER_CURL_VTLS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +struct connectdata; +struct ssl_config_data; +struct ssl_primary_config; +struct Curl_ssl_session; + +#define SSLSUPP_CA_PATH (1<<0) /* supports CAPATH */ +#define SSLSUPP_CERTINFO (1<<1) /* supports CURLOPT_CERTINFO */ +#define SSLSUPP_PINNEDPUBKEY (1<<2) /* supports CURLOPT_PINNEDPUBLICKEY */ +#define SSLSUPP_SSL_CTX (1<<3) /* supports CURLOPT_SSL_CTX */ +#define SSLSUPP_HTTPS_PROXY (1<<4) /* supports access via HTTPS proxies */ +#define SSLSUPP_TLS13_CIPHERSUITES (1<<5) /* supports TLS 1.3 ciphersuites */ +#define SSLSUPP_CAINFO_BLOB (1<<6) + +#define ALPN_ACCEPTED "ALPN: server accepted " + +#define VTLS_INFOF_NO_ALPN \ + "ALPN: server did not agree on a protocol. Uses default." +#define VTLS_INFOF_ALPN_OFFER_1STR \ + "ALPN: curl offers %s" +#define VTLS_INFOF_ALPN_ACCEPTED_1STR \ + ALPN_ACCEPTED "%s" +#define VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR \ + ALPN_ACCEPTED "%.*s" + +/* Curl_multi SSL backend-specific data; declared differently by each SSL + backend */ +struct multi_ssl_backend_data; +struct Curl_cfilter; + +CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, + const curl_ssl_backend ***avail); + +#ifndef MAX_PINNED_PUBKEY_SIZE +#define MAX_PINNED_PUBKEY_SIZE 1048576 /* 1MB */ +#endif + +#ifndef CURL_SHA256_DIGEST_LENGTH +#define CURL_SHA256_DIGEST_LENGTH 32 /* fixed size */ +#endif + +curl_sslbackend Curl_ssl_backend(void); + +/** + * Init ssl config for a new easy handle. + */ +void Curl_ssl_easy_config_init(struct Curl_easy *data); + +/** + * Init the `data->set.ssl` and `data->set.proxy_ssl` for + * connection matching use. + */ +CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data); + +/** + * Init SSL configs (main + proxy) for a new connection from the easy handle. + */ +CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, + struct connectdata *conn); + +/** + * Free allocated resources in SSL configs (main + proxy) for + * the given connection. + */ +void Curl_ssl_conn_config_cleanup(struct connectdata *conn); + +/** + * Return TRUE iff SSL configuration from `conn` is functionally the + * same as the one on `candidate`. + * @param proxy match the proxy SSL config or the main one + */ +bool Curl_ssl_conn_config_match(struct Curl_easy *data, + struct connectdata *candidate, + bool proxy); + +/* Update certain connection SSL config flags after they have + * been changed on the easy handle. Will work for `verifypeer`, + * `verifyhost` and `verifystatus`. */ +void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy); + +/** + * Init SSL peer information for filter. Can be called repeatedly. + */ +CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, struct Curl_cfilter *cf); +/** + * Free all allocated data and reset peer information. + */ +void Curl_ssl_peer_cleanup(struct ssl_peer *peer); + +#ifdef USE_SSL +int Curl_ssl_init(void); +void Curl_ssl_cleanup(void); +/* tell the SSL stuff to close down all open information regarding + connections (and thus session ID caching etc) */ +void Curl_ssl_close_all(struct Curl_easy *data); +CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine); +/* Sets engine as default for all SSL operations */ +CURLcode Curl_ssl_set_engine_default(struct Curl_easy *data); +struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data); + +/* init the SSL session ID cache */ +CURLcode Curl_ssl_initsessions(struct Curl_easy *, size_t); +void Curl_ssl_version(char *buffer, size_t size); + +/* Certificate information list handling. */ + +void Curl_ssl_free_certinfo(struct Curl_easy *data); +CURLcode Curl_ssl_init_certinfo(struct Curl_easy *data, int num); +CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, int certnum, + const char *label, const char *value, + size_t valuelen); +CURLcode Curl_ssl_push_certinfo(struct Curl_easy *data, int certnum, + const char *label, const char *value); + +/* Functions to be used by SSL library adaptation functions */ + +/* Lock session cache mutex. + * Call this before calling other Curl_ssl_*session* functions + * Caller should unlock this mutex as soon as possible, as it may block + * other SSL connection from making progress. + * The purpose of explicitly locking SSL session cache data is to allow + * individual SSL engines to manage session lifetime in their specific way. + */ +void Curl_ssl_sessionid_lock(struct Curl_easy *data); + +/* Unlock session cache mutex */ +void Curl_ssl_sessionid_unlock(struct Curl_easy *data); + +/* Kill a single session ID entry in the cache + * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). + * This will call engine-specific curlssl_session_free function, which must + * take sessionid object ownership from sessionid cache + * (e.g. decrement refcount). + */ +void Curl_ssl_kill_session(struct Curl_ssl_session *session); +/* delete a session from the cache + * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). + * This will call engine-specific curlssl_session_free function, which must + * take sessionid object ownership from sessionid cache + * (e.g. decrement refcount). + */ +void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid); + +/* get N random bytes into the buffer */ +CURLcode Curl_ssl_random(struct Curl_easy *data, unsigned char *buffer, + size_t length); +/* Check pinned public key. */ +CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, + const char *pinnedpubkey, + const unsigned char *pubkey, size_t pubkeylen); + +bool Curl_ssl_cert_status_request(void); + +bool Curl_ssl_false_start(struct Curl_easy *data); + +void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend); + +#define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */ + +CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + +CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data); + +CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, + int sockindex); + +#ifndef CURL_DISABLE_PROXY +CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data); +#endif /* !CURL_DISABLE_PROXY */ + +/** + * True iff the underlying SSL implementation supports the option. + * Option is one of the defined SSLSUPP_* values. + * `data` maybe NULL for the features of the default implementation. + */ +bool Curl_ssl_supports(struct Curl_easy *data, int ssl_option); + +/** + * Get the internal ssl instance (like OpenSSL's SSL*) from the filter + * chain at `sockindex` of type specified by `info`. + * For `n` == 0, the first active (top down) instance is returned. + * 1 gives the second active, etc. + * NULL is returned when no active SSL filter is present. + */ +void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, + CURLINFO info, int n); + +/** + * Get the ssl_config_data in `data` that is relevant for cfilter `cf`. + */ +struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf, + struct Curl_easy *data); + +/** + * Get the primary config relevant for the filter from its connection. + */ +struct ssl_primary_config * + Curl_ssl_cf_get_primary_config(struct Curl_cfilter *cf); + +extern struct Curl_cftype Curl_cft_ssl; +extern struct Curl_cftype Curl_cft_ssl_proxy; + +#else /* if not USE_SSL */ + +/* When SSL support is not present, just define away these function calls */ +#define Curl_ssl_init() 1 +#define Curl_ssl_cleanup() Curl_nop_stmt +#define Curl_ssl_close_all(x) Curl_nop_stmt +#define Curl_ssl_set_engine(x,y) CURLE_NOT_BUILT_IN +#define Curl_ssl_set_engine_default(x) CURLE_NOT_BUILT_IN +#define Curl_ssl_engines_list(x) NULL +#define Curl_ssl_initsessions(x,y) CURLE_OK +#define Curl_ssl_free_certinfo(x) Curl_nop_stmt +#define Curl_ssl_kill_session(x) Curl_nop_stmt +#define Curl_ssl_random(x,y,z) ((void)x, CURLE_NOT_BUILT_IN) +#define Curl_ssl_cert_status_request() FALSE +#define Curl_ssl_false_start(a) FALSE +#define Curl_ssl_get_internals(a,b,c,d) NULL +#define Curl_ssl_supports(a,b) FALSE +#define Curl_ssl_cfilter_add(a,b,c) CURLE_NOT_BUILT_IN +#define Curl_ssl_cfilter_remove(a,b) CURLE_OK +#define Curl_ssl_cf_get_config(a,b) NULL +#define Curl_ssl_cf_get_primary_config(a) NULL +#endif + +#endif /* HEADER_CURL_VTLS_H */ diff --git a/Utilities/cmcurl/lib/vtls/vtls_int.h b/Utilities/cmcurl/lib/vtls/vtls_int.h new file mode 100644 index 0000000..af7ae55 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/vtls_int.h @@ -0,0 +1,207 @@ +#ifndef HEADER_CURL_VTLS_INT_H +#define HEADER_CURL_VTLS_INT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" +#include "cfilters.h" +#include "urldata.h" + +#ifdef USE_SSL + +/* see https://www.iana.org/assignments/tls-extensiontype-values/ */ +#define ALPN_HTTP_1_1_LENGTH 8 +#define ALPN_HTTP_1_1 "http/1.1" +#define ALPN_H2_LENGTH 2 +#define ALPN_H2 "h2" +#define ALPN_H3_LENGTH 2 +#define ALPN_H3 "h3" + +/* conservative sizes on the ALPN entries and count we are handling, + * we can increase these if we ever feel the need or have to accommodate + * ALPN strings from the "outside". */ +#define ALPN_NAME_MAX 10 +#define ALPN_ENTRIES_MAX 3 +#define ALPN_PROTO_BUF_MAX (ALPN_ENTRIES_MAX * (ALPN_NAME_MAX + 1)) + +struct alpn_spec { + const char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX]; + size_t count; /* number of entries */ +}; + +struct alpn_proto_buf { + unsigned char data[ALPN_PROTO_BUF_MAX]; + int len; +}; + +CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf, + const struct alpn_spec *spec); +CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf, + const struct alpn_spec *spec); + +CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, + struct Curl_easy *data, + const unsigned char *proto, + size_t proto_len); + +/* Information in each SSL cfilter context: cf->ctx */ +struct ssl_connect_data { + ssl_connection_state state; + ssl_connect_state connecting_state; + struct ssl_peer peer; + 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 curltime handshake_done; /* time when handshake finished */ + int port; /* remote port at origin */ + BIT(use_alpn); /* if ALPN shall be used in handshake */ + BIT(reused_session); /* session-ID was reused for this */ +}; + + +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) \ + ((struct ssl_connect_data *)(cf)->ctx)->call_data + + +/* Definitions for SSL Implementations */ + +struct Curl_ssl { + /* + * This *must* be the first entry to allow returning the list of available + * backends in curl_global_sslset(). + */ + curl_ssl_backend info; + unsigned int supports; /* bitfield, see above */ + size_t sizeof_ssl_backend_data; + + int (*init)(void); + void (*cleanup)(void); + + size_t (*version)(char *buffer, size_t size); + int (*check_cxn)(struct Curl_cfilter *cf, struct Curl_easy *data); + int (*shut_down)(struct Curl_cfilter *cf, + struct Curl_easy *data); + bool (*data_pending)(struct Curl_cfilter *cf, + const struct Curl_easy *data); + + /* return 0 if a find random is filled in */ + CURLcode (*random)(struct Curl_easy *data, unsigned char *entropy, + size_t length); + bool (*cert_status_request)(void); + + CURLcode (*connect_blocking)(struct Curl_cfilter *cf, + struct Curl_easy *data); + CURLcode (*connect_nonblocking)(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done); + + /* During handshake, adjust the pollset to include the socket + * for POLLOUT or POLLIN as needed. + * Mandatory. */ + void (*adjust_pollset)(struct Curl_cfilter *cf, struct Curl_easy *data, + struct easy_pollset *ps); + void *(*get_internals)(struct ssl_connect_data *connssl, CURLINFO info); + void (*close)(struct Curl_cfilter *cf, struct Curl_easy *data); + void (*close_all)(struct Curl_easy *data); + void (*session_free)(void *ptr); + + CURLcode (*set_engine)(struct Curl_easy *data, const char *engine); + CURLcode (*set_engine_default)(struct Curl_easy *data); + struct curl_slist *(*engines_list)(struct Curl_easy *data); + + bool (*false_start)(void); + CURLcode (*sha256sum)(const unsigned char *input, size_t inputlen, + unsigned char *sha256sum, size_t sha256sumlen); + + bool (*attach_data)(struct Curl_cfilter *cf, struct Curl_easy *data); + void (*detach_data)(struct Curl_cfilter *cf, struct Curl_easy *data); + + void (*free_multi_ssl_backend_data)(struct multi_ssl_backend_data *mbackend); + + ssize_t (*recv_plain)(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *code); + ssize_t (*send_plain)(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *mem, size_t len, CURLcode *code); + +}; + +extern const struct Curl_ssl *Curl_ssl; + + +int Curl_none_init(void); +void Curl_none_cleanup(void); +int Curl_none_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data); +int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data); +CURLcode Curl_none_random(struct Curl_easy *data, unsigned char *entropy, + size_t length); +void Curl_none_close_all(struct Curl_easy *data); +void Curl_none_session_free(void *ptr); +bool Curl_none_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data); +bool Curl_none_cert_status_request(void); +CURLcode Curl_none_set_engine(struct Curl_easy *data, const char *engine); +CURLcode Curl_none_set_engine_default(struct Curl_easy *data); +struct curl_slist *Curl_none_engines_list(struct Curl_easy *data); +bool Curl_none_false_start(void); +void Curl_ssl_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, + struct easy_pollset *ps); + +/** + * Get the SSL filter below the given one or NULL if there is none. + */ +bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf); + +/* extract a session ID + * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). + * Caller must make sure that the ownership of returned sessionid object + * is properly taken (e.g. its refcount is incremented + * under sessionid mutex). + */ +bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, + struct Curl_easy *data, + void **ssl_sessionid, + size_t *idsize); /* set 0 if unknown */ +/* add a new session ID + * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). + * Caller must ensure that it has properly shared ownership of this sessionid + * object with cache (e.g. incrementing refcount on success) + */ +CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *ssl_sessionid, + size_t idsize, + bool *added); + +#include "openssl.h" /* OpenSSL versions */ +#include "gtls.h" /* GnuTLS versions */ +#include "wolfssl.h" /* wolfSSL versions */ +#include "schannel.h" /* Schannel SSPI version */ +#include "sectransp.h" /* SecureTransport (Darwin) version */ +#include "mbedtls.h" /* mbedTLS versions */ +#include "bearssl.h" /* BearSSL versions */ +#include "rustls.h" /* rustls versions */ + +#endif /* USE_SSL */ + +#endif /* HEADER_CURL_VTLS_INT_H */ diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.c b/Utilities/cmcurl/lib/vtls/wolfssl.c new file mode 100644 index 0000000..5890bb6 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/wolfssl.c @@ -0,0 +1,1407 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Source file for all wolfSSL specific code for the TLS/SSL layer. No code + * but vtls.c should ever call or use these functions. + * + */ + +#include "curl_setup.h" + +#ifdef USE_WOLFSSL + +#define WOLFSSL_OPTIONS_IGNORE_SYS +#include <wolfssl/version.h> +#include <wolfssl/options.h> + +/* To determine what functions are available we rely on one or both of: + - the user's options.h generated by wolfSSL + - the symbols detected by curl's configure + Since they are markedly different from one another, and one or the other may + not be available, we do some checking below to bring things in sync. */ + +/* HAVE_ALPN is wolfSSL's build time symbol for enabling ALPN in options.h. */ +#ifndef HAVE_ALPN +#ifdef HAVE_WOLFSSL_USEALPN +#define HAVE_ALPN +#endif +#endif + +#include <limits.h> + +#include "urldata.h" +#include "sendf.h" +#include "inet_pton.h" +#include "vtls.h" +#include "vtls_int.h" +#include "keylog.h" +#include "parsedate.h" +#include "connect.h" /* for the connect timeout */ +#include "select.h" +#include "strcase.h" +#include "x509asn1.h" +#include "curl_printf.h" +#include "multiif.h" + +#include <wolfssl/openssl/ssl.h> +#include <wolfssl/ssl.h> +#include <wolfssl/error-ssl.h> +#include "wolfssl.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +/* KEEP_PEER_CERT is a product of the presence of build time symbol + OPENSSL_EXTRA without NO_CERTS, depending on the version. KEEP_PEER_CERT is + in wolfSSL's settings.h, and the latter two are build time symbols in + options.h. */ +#ifndef KEEP_PEER_CERT +#if defined(HAVE_WOLFSSL_GET_PEER_CERTIFICATE) || \ + (defined(OPENSSL_EXTRA) && !defined(NO_CERTS)) +#define KEEP_PEER_CERT +#endif +#endif + +#if defined(HAVE_WOLFSSL_FULL_BIO) && HAVE_WOLFSSL_FULL_BIO +#define USE_BIO_CHAIN +#else +#undef USE_BIO_CHAIN +#endif + +struct wolfssl_ssl_backend_data { + WOLFSSL_CTX *ctx; + WOLFSSL *handle; + CURLcode io_result; /* result of last BIO cfilter operation */ +}; + +#ifdef OPENSSL_EXTRA +/* + * Availability note: + * The TLS 1.3 secret callback (wolfSSL_set_tls13_secret_cb) was added in + * WolfSSL 4.4.0, but requires the -DHAVE_SECRET_CALLBACK build option. If that + * option is not set, then TLS 1.3 will not be logged. + * For TLS 1.2 and before, we use wolfSSL_get_keys(). + * SSL_get_client_random and wolfSSL_get_keys require OPENSSL_EXTRA + * (--enable-opensslextra or --enable-all). + */ +#if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) +static int +wolfssl_tls13_secret_callback(SSL *ssl, int id, const unsigned char *secret, + int secretSz, void *ctx) +{ + const char *label; + unsigned char client_random[SSL3_RANDOM_SIZE]; + (void)ctx; + + if(!ssl || !Curl_tls_keylog_enabled()) { + return 0; + } + + switch(id) { + case CLIENT_EARLY_TRAFFIC_SECRET: + label = "CLIENT_EARLY_TRAFFIC_SECRET"; + break; + case CLIENT_HANDSHAKE_TRAFFIC_SECRET: + label = "CLIENT_HANDSHAKE_TRAFFIC_SECRET"; + break; + case SERVER_HANDSHAKE_TRAFFIC_SECRET: + label = "SERVER_HANDSHAKE_TRAFFIC_SECRET"; + break; + case CLIENT_TRAFFIC_SECRET: + label = "CLIENT_TRAFFIC_SECRET_0"; + break; + case SERVER_TRAFFIC_SECRET: + label = "SERVER_TRAFFIC_SECRET_0"; + break; + case EARLY_EXPORTER_SECRET: + label = "EARLY_EXPORTER_SECRET"; + break; + case EXPORTER_SECRET: + label = "EXPORTER_SECRET"; + break; + default: + return 0; + } + + if(SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE) == 0) { + /* Should never happen as wolfSSL_KeepArrays() was called before. */ + return 0; + } + + Curl_tls_keylog_write(label, client_random, secret, secretSz); + return 0; +} +#endif /* defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) */ + +static void +wolfssl_log_tls12_secret(SSL *ssl) +{ + unsigned char *ms, *sr, *cr; + unsigned int msLen, srLen, crLen, i, x = 0; + +#if LIBWOLFSSL_VERSION_HEX >= 0x0300d000 /* >= 3.13.0 */ + /* wolfSSL_GetVersion is available since 3.13, we use it instead of + * SSL_version since the latter relies on OPENSSL_ALL (--enable-opensslall or + * --enable-all). Failing to perform this check could result in an unusable + * key log line when TLS 1.3 is actually negotiated. */ + switch(wolfSSL_GetVersion(ssl)) { + case WOLFSSL_SSLV3: + case WOLFSSL_TLSV1: + case WOLFSSL_TLSV1_1: + case WOLFSSL_TLSV1_2: + break; + default: + /* TLS 1.3 does not use this mechanism, the "master secret" returned below + * is not directly usable. */ + return; + } +#endif + + if(wolfSSL_get_keys(ssl, &ms, &msLen, &sr, &srLen, &cr, &crLen) != + SSL_SUCCESS) { + return; + } + + /* Check for a missing master secret and skip logging. That can happen if + * curl rejects the server certificate and aborts the handshake. + */ + for(i = 0; i < msLen; i++) { + x |= ms[i]; + } + if(x == 0) { + return; + } + + Curl_tls_keylog_write("CLIENT_RANDOM", cr, ms, msLen); +} +#endif /* OPENSSL_EXTRA */ + +static int do_file_type(const char *type) +{ + if(!type || !type[0]) + return SSL_FILETYPE_PEM; + if(strcasecompare(type, "PEM")) + return SSL_FILETYPE_PEM; + if(strcasecompare(type, "DER")) + return SSL_FILETYPE_ASN1; + return -1; +} + +#ifdef HAVE_LIBOQS +struct group_name_map { + const word16 group; + const char *name; +}; + +static const struct group_name_map gnm[] = { + { WOLFSSL_KYBER_LEVEL1, "KYBER_LEVEL1" }, + { WOLFSSL_KYBER_LEVEL3, "KYBER_LEVEL3" }, + { WOLFSSL_KYBER_LEVEL5, "KYBER_LEVEL5" }, + { WOLFSSL_P256_KYBER_LEVEL1, "P256_KYBER_LEVEL1" }, + { WOLFSSL_P384_KYBER_LEVEL3, "P384_KYBER_LEVEL3" }, + { WOLFSSL_P521_KYBER_LEVEL5, "P521_KYBER_LEVEL5" }, + { 0, NULL } +}; +#endif + +#ifdef USE_BIO_CHAIN + +static int wolfssl_bio_cf_create(WOLFSSL_BIO *bio) +{ + wolfSSL_BIO_set_shutdown(bio, 1); + wolfSSL_BIO_set_init(bio, 1); + wolfSSL_BIO_set_data(bio, NULL); + return 1; +} + +static int wolfssl_bio_cf_destroy(WOLFSSL_BIO *bio) +{ + if(!bio) + return 0; + return 1; +} + +static long wolfssl_bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) +{ + struct Curl_cfilter *cf = BIO_get_data(bio); + long ret = 1; + + (void)cf; + (void)ptr; + switch(cmd) { + case BIO_CTRL_GET_CLOSE: + ret = (long)wolfSSL_BIO_get_shutdown(bio); + break; + case BIO_CTRL_SET_CLOSE: + wolfSSL_BIO_set_shutdown(bio, (int)num); + break; + case BIO_CTRL_FLUSH: + /* we do no delayed writes, but if we ever would, this + * needs to trigger it. */ + ret = 1; + break; + case BIO_CTRL_DUP: + ret = 1; + break; +#ifdef BIO_CTRL_EOF + case BIO_CTRL_EOF: + /* EOF has been reached on input? */ + return (!cf->next || !cf->next->connected); +#endif + default: + ret = 0; + break; + } + return ret; +} + +static int wolfssl_bio_cf_out_write(WOLFSSL_BIO *bio, + const char *buf, int blen) +{ + struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ssl_backend_data *backend = + (struct wolfssl_ssl_backend_data *)connssl->backend; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nwritten; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); + backend->io_result = result; + CURL_TRC_CF(data, cf, "bio_write(len=%d) -> %zd, %d", + blen, nwritten, result); + wolfSSL_BIO_clear_retry_flags(bio); + if(nwritten < 0 && CURLE_AGAIN == result) + BIO_set_retry_write(bio); + return (int)nwritten; +} + +static int wolfssl_bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) +{ + struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ssl_backend_data *backend = + (struct wolfssl_ssl_backend_data *)connssl->backend; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nread; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + /* OpenSSL catches this case, so should we. */ + if(!buf) + return 0; + + nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); + backend->io_result = result; + CURL_TRC_CF(data, cf, "bio_read(len=%d) -> %zd, %d", blen, nread, result); + wolfSSL_BIO_clear_retry_flags(bio); + if(nread < 0 && CURLE_AGAIN == result) + BIO_set_retry_read(bio); + return (int)nread; +} + +static WOLFSSL_BIO_METHOD *wolfssl_bio_cf_method = NULL; + +static void wolfssl_bio_cf_init_methods(void) +{ + wolfssl_bio_cf_method = wolfSSL_BIO_meth_new(BIO_TYPE_MEM, "wolfSSL CF BIO"); + wolfSSL_BIO_meth_set_write(wolfssl_bio_cf_method, &wolfssl_bio_cf_out_write); + wolfSSL_BIO_meth_set_read(wolfssl_bio_cf_method, &wolfssl_bio_cf_in_read); + wolfSSL_BIO_meth_set_ctrl(wolfssl_bio_cf_method, &wolfssl_bio_cf_ctrl); + wolfSSL_BIO_meth_set_create(wolfssl_bio_cf_method, &wolfssl_bio_cf_create); + wolfSSL_BIO_meth_set_destroy(wolfssl_bio_cf_method, &wolfssl_bio_cf_destroy); +} + +static void wolfssl_bio_cf_free_methods(void) +{ + wolfSSL_BIO_meth_free(wolfssl_bio_cf_method); +} + +#else /* USE_BIO_CHAIN */ + +#define wolfssl_bio_cf_init_methods() Curl_nop_stmt +#define wolfssl_bio_cf_free_methods() Curl_nop_stmt + +#endif /* !USE_BIO_CHAIN */ + +/* + * This function loads all the client/CA certificates and CRLs. Setup the TLS + * layer and do all necessary magic. + */ +static CURLcode +wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + char *ciphers, *curves; + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ssl_backend_data *backend = + (struct wolfssl_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + const char * const ssl_cafile = + /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ + (ca_info_blob ? NULL : conn_config->CAfile); + const char * const ssl_capath = conn_config->CApath; + WOLFSSL_METHOD* req_method = NULL; +#ifdef HAVE_LIBOQS + word16 oqsAlg = 0; + size_t idx = 0; +#endif +#ifdef HAVE_SNI + bool sni = FALSE; +#define use_sni(x) sni = (x) +#else +#define use_sni(x) Curl_nop_stmt +#endif + bool imported_native_ca = false; + bool imported_ca_info_blob = false; + + DEBUGASSERT(backend); + + if(connssl->state == ssl_connection_complete) + return CURLE_OK; + + if(conn_config->version_max != CURL_SSLVERSION_MAX_NONE) { + failf(data, "wolfSSL does not support to set maximum SSL/TLS version"); + return CURLE_SSL_CONNECT_ERROR; + } + + /* check to see if we've been told to use an explicit SSL/TLS version */ + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: +#if LIBWOLFSSL_VERSION_HEX >= 0x03003000 /* >= 3.3.0 */ + /* minimum protocol version is set later after the CTX object is created */ + req_method = SSLv23_client_method(); +#else + infof(data, "wolfSSL <3.3.0 cannot be configured to use TLS 1.0-1.2, " + "TLS 1.0 is used exclusively"); + req_method = TLSv1_client_method(); +#endif + use_sni(TRUE); + break; + case CURL_SSLVERSION_TLSv1_0: +#if defined(WOLFSSL_ALLOW_TLSV10) && !defined(NO_OLD_TLS) + req_method = TLSv1_client_method(); + use_sni(TRUE); +#else + failf(data, "wolfSSL does not support TLS 1.0"); + return CURLE_NOT_BUILT_IN; +#endif + break; + case CURL_SSLVERSION_TLSv1_1: +#ifndef NO_OLD_TLS + req_method = TLSv1_1_client_method(); + use_sni(TRUE); +#else + failf(data, "wolfSSL does not support TLS 1.1"); + return CURLE_NOT_BUILT_IN; +#endif + break; + case CURL_SSLVERSION_TLSv1_2: +#ifndef WOLFSSL_NO_TLS12 + req_method = TLSv1_2_client_method(); + use_sni(TRUE); +#else + failf(data, "wolfSSL does not support TLS 1.2"); + return CURLE_NOT_BUILT_IN; +#endif + break; + case CURL_SSLVERSION_TLSv1_3: +#ifdef WOLFSSL_TLS13 + req_method = wolfTLSv1_3_client_method(); + use_sni(TRUE); + break; +#else + failf(data, "wolfSSL: TLS 1.3 is not yet supported"); + return CURLE_SSL_CONNECT_ERROR; +#endif + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(!req_method) { + failf(data, "SSL: couldn't create a method"); + return CURLE_OUT_OF_MEMORY; + } + + if(backend->ctx) + wolfSSL_CTX_free(backend->ctx); + backend->ctx = wolfSSL_CTX_new(req_method); + + if(!backend->ctx) { + failf(data, "SSL: couldn't create a context"); + return CURLE_OUT_OF_MEMORY; + } + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: +#if LIBWOLFSSL_VERSION_HEX > 0x03004006 /* > 3.4.6 */ + /* Versions 3.3.0 to 3.4.6 we know the minimum protocol version is + * whatever minimum version of TLS was built in and at least TLS 1.0. For + * later library versions that could change (eg TLS 1.0 built in but + * defaults to TLS 1.1) so we have this short circuit evaluation to find + * the minimum supported TLS version. + */ + if((wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1) != 1) && + (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_1) != 1) && + (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_2) != 1) +#ifdef WOLFSSL_TLS13 + && (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_3) != 1) +#endif + ) { + failf(data, "SSL: couldn't set the minimum protocol version"); + return CURLE_SSL_CONNECT_ERROR; + } +#endif + default: + break; + } + + ciphers = conn_config->cipher_list; + if(ciphers) { + if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) { + failf(data, "failed setting cipher list: %s", ciphers); + return CURLE_SSL_CIPHER; + } + infof(data, "Cipher selection: %s", ciphers); + } + + curves = conn_config->curves; + if(curves) { + +#ifdef HAVE_LIBOQS + for(idx = 0; gnm[idx].name != NULL; idx++) { + if(strncmp(curves, gnm[idx].name, strlen(gnm[idx].name)) == 0) { + oqsAlg = gnm[idx].group; + break; + } + } + + if(oqsAlg == 0) +#endif + { + if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) { + failf(data, "failed setting curves list: '%s'", curves); + return CURLE_SSL_CIPHER; + } + } + } + +#if !defined(NO_FILESYSTEM) && defined(WOLFSSL_SYS_CA_CERTS) + /* load native CA certificates */ + if(ssl_config->native_ca_store) { + if(wolfSSL_CTX_load_system_CA_certs(backend->ctx) != WOLFSSL_SUCCESS) { + infof(data, "error importing native CA store, continuing anyway"); + } + else { + imported_native_ca = true; + infof(data, "successfully imported native CA store"); + } + } +#endif /* !NO_FILESYSTEM */ + + /* load certificate blob */ + if(ca_info_blob) { + if(wolfSSL_CTX_load_verify_buffer(backend->ctx, ca_info_blob->data, + ca_info_blob->len, + SSL_FILETYPE_PEM) != SSL_SUCCESS) { + if(imported_native_ca) { + infof(data, "error importing CA certificate blob, continuing anyway"); + } + else { + failf(data, "error importing CA certificate blob"); + return CURLE_SSL_CACERT_BADFILE; + } + } + else { + imported_ca_info_blob = true; + infof(data, "successfully imported CA certificate blob"); + } + } + +#ifndef NO_FILESYSTEM + /* load trusted cacert from file if not blob */ + if(ssl_cafile || ssl_capath) { + int rc = + wolfSSL_CTX_load_verify_locations_ex(backend->ctx, + ssl_cafile, + ssl_capath, + WOLFSSL_LOAD_FLAG_IGNORE_ERR); + if(SSL_SUCCESS != rc) { + if(conn_config->verifypeer && !imported_ca_info_blob && + !imported_native_ca) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate verify locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + return CURLE_SSL_CACERT_BADFILE; + } + else { + /* Just continue with a warning if no strict certificate + verification is required. */ + infof(data, "error setting certificate verify locations," + " continuing anyway:"); + } + } + else { + /* Everything is fine. */ + infof(data, "successfully set certificate verify locations:"); + } + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + } + + /* Load the client certificate, and private key */ + if(ssl_config->primary.clientcert && ssl_config->key) { + int file_type = do_file_type(ssl_config->cert_type); + + if(wolfSSL_CTX_use_certificate_file(backend->ctx, + ssl_config->primary.clientcert, + file_type) != 1) { + failf(data, "unable to use client certificate (no key or wrong pass" + " phrase?)"); + return CURLE_SSL_CONNECT_ERROR; + } + + file_type = do_file_type(ssl_config->key_type); + if(wolfSSL_CTX_use_PrivateKey_file(backend->ctx, ssl_config->key, + file_type) != 1) { + failf(data, "unable to set private key"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif /* !NO_FILESYSTEM */ + + /* SSL always tries to verify the peer, this only says whether it should + * fail to connect if the verification fails, or if it should continue + * anyway. In the latter case the result of the verification is checked with + * SSL_get_verify_result() below. */ + wolfSSL_CTX_set_verify(backend->ctx, + conn_config->verifypeer?SSL_VERIFY_PEER: + SSL_VERIFY_NONE, NULL); + +#ifdef HAVE_SNI + if(sni && connssl->peer.sni) { + size_t sni_len = strlen(connssl->peer.sni); + if((sni_len < USHRT_MAX)) { + if(wolfSSL_CTX_UseSNI(backend->ctx, WOLFSSL_SNI_HOST_NAME, + connssl->peer.sni, + (unsigned short)sni_len) != 1) { + failf(data, "Failed to set SNI"); + return CURLE_SSL_CONNECT_ERROR; + } + } + } +#endif + + /* give application a chance to interfere with SSL set up. */ + if(data->set.ssl.fsslctx) { + CURLcode result = (*data->set.ssl.fsslctx)(data, backend->ctx, + data->set.ssl.fsslctxp); + if(result) { + failf(data, "error signaled by ssl ctx callback"); + return result; + } + } +#ifdef NO_FILESYSTEM + else if(conn_config->verifypeer) { + failf(data, "SSL: Certificates can't be loaded because wolfSSL was built" + " with \"no filesystem\". Either disable peer verification" + " (insecure) or if you are building an application with libcurl you" + " can load certificates via CURLOPT_SSL_CTX_FUNCTION."); + return CURLE_SSL_CONNECT_ERROR; + } +#endif + + /* Let's make an SSL structure */ + if(backend->handle) + wolfSSL_free(backend->handle); + backend->handle = wolfSSL_new(backend->ctx); + if(!backend->handle) { + failf(data, "SSL: couldn't create a handle"); + return CURLE_OUT_OF_MEMORY; + } + +#ifdef HAVE_LIBOQS + if(oqsAlg) { + if(wolfSSL_UseKeyShare(backend->handle, oqsAlg) != WOLFSSL_SUCCESS) { + failf(data, "unable to use oqs KEM"); + } + } +#endif + +#ifdef HAVE_ALPN + if(connssl->alpn) { + struct alpn_proto_buf proto; + CURLcode result; + + result = Curl_alpn_to_proto_str(&proto, connssl->alpn); + if(result || + wolfSSL_UseALPN(backend->handle, (char *)proto.data, proto.len, + WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != SSL_SUCCESS) { + failf(data, "SSL: failed setting ALPN protocols"); + return CURLE_SSL_CONNECT_ERROR; + } + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } +#endif /* HAVE_ALPN */ + +#ifdef OPENSSL_EXTRA + if(Curl_tls_keylog_enabled()) { + /* Ensure the Client Random is preserved. */ + wolfSSL_KeepArrays(backend->handle); +#if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) + wolfSSL_set_tls13_secret_cb(backend->handle, + wolfssl_tls13_secret_callback, NULL); +#endif + } +#endif /* OPENSSL_EXTRA */ + +#ifdef HAVE_SECURE_RENEGOTIATION + if(wolfSSL_UseSecureRenegotiation(backend->handle) != SSL_SUCCESS) { + failf(data, "SSL: failed setting secure renegotiation"); + return CURLE_SSL_CONNECT_ERROR; + } +#endif /* HAVE_SECURE_RENEGOTIATION */ + + /* Check if there's a cached ID we can/should use here! */ + if(ssl_config->primary.sessionid) { + void *ssl_sessionid = NULL; + + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)) { + /* we got a session id, use it! */ + if(!SSL_set_session(backend->handle, ssl_sessionid)) { + Curl_ssl_delsessionid(data, ssl_sessionid); + infof(data, "Can't use session ID, going on without"); + } + else + infof(data, "SSL reusing session ID"); + } + Curl_ssl_sessionid_unlock(data); + } + +#ifdef USE_BIO_CHAIN + { + WOLFSSL_BIO *bio; + + bio = BIO_new(wolfssl_bio_cf_method); + if(!bio) + return CURLE_OUT_OF_MEMORY; + + wolfSSL_BIO_set_data(bio, cf); + wolfSSL_set_bio(backend->handle, bio, bio); + } +#else /* USE_BIO_CHAIN */ + /* pass the raw socket into the SSL layer */ + if(!wolfSSL_set_fd(backend->handle, + (int)Curl_conn_cf_get_socket(cf, data))) { + failf(data, "SSL: SSL_set_fd failed"); + return CURLE_SSL_CONNECT_ERROR; + } +#endif /* !USE_BIO_CHAIN */ + + connssl->connecting_state = ssl_connect_2; + return CURLE_OK; +} + + +static CURLcode +wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + int ret = -1; + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ssl_backend_data *backend = + (struct wolfssl_ssl_backend_data *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf)? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + + DEBUGASSERT(backend); + + wolfSSL_ERR_clear_error(); + + /* Enable RFC2818 checks */ + if(conn_config->verifyhost) { + char *snihost = connssl->peer.sni? + connssl->peer.sni : connssl->peer.hostname; + if(wolfSSL_check_domain_name(backend->handle, snihost) == SSL_FAILURE) + return CURLE_SSL_CONNECT_ERROR; + } + + ret = wolfSSL_connect(backend->handle); + +#ifdef OPENSSL_EXTRA + if(Curl_tls_keylog_enabled()) { + /* If key logging is enabled, wait for the handshake to complete and then + * proceed with logging secrets (for TLS 1.2 or older). + * + * During the handshake (ret==-1), wolfSSL_want_read() is true as it waits + * for the server response. At that point the master secret is not yet + * available, so we must not try to read it. + * To log the secret on completion with a handshake failure, detect + * completion via the observation that there is nothing to read or write. + * Note that OpenSSL SSL_want_read() is always true here. If wolfSSL ever + * changes, the worst case is that no key is logged on error. + */ + if(ret == SSL_SUCCESS || + (!wolfSSL_want_read(backend->handle) && + !wolfSSL_want_write(backend->handle))) { + wolfssl_log_tls12_secret(backend->handle); + /* Client Random and master secrets are no longer needed, erase these. + * Ignored while the handshake is still in progress. */ + wolfSSL_FreeArrays(backend->handle); + } + } +#endif /* OPENSSL_EXTRA */ + + if(ret != 1) { + char error_buffer[WOLFSSL_MAX_ERROR_SZ]; + int detail = wolfSSL_get_error(backend->handle, ret); + + if(SSL_ERROR_WANT_READ == detail) { + connssl->connecting_state = ssl_connect_2_reading; + return CURLE_OK; + } + else if(SSL_ERROR_WANT_WRITE == detail) { + connssl->connecting_state = ssl_connect_2_writing; + return CURLE_OK; + } + /* There is no easy way to override only the CN matching. + * This will enable the override of both mismatching SubjectAltNames + * as also mismatching CN fields */ + else if(DOMAIN_NAME_MISMATCH == detail) { +#if 1 + failf(data, " subject alt name(s) or common name do not match \"%s\"", + connssl->peer.dispname); + return CURLE_PEER_FAILED_VERIFICATION; +#else + /* When the wolfssl_check_domain_name() is used and you desire to + * continue on a DOMAIN_NAME_MISMATCH, i.e. 'ssl_config.verifyhost + * == 0', CyaSSL version 2.4.0 will fail with an INCOMPLETE_DATA + * error. The only way to do this is currently to switch the + * Wolfssl_check_domain_name() in and out based on the + * 'ssl_config.verifyhost' value. */ + if(conn_config->verifyhost) { + failf(data, + " subject alt name(s) or common name do not match \"%s\"\n", + connssl->dispname); + return CURLE_PEER_FAILED_VERIFICATION; + } + else { + infof(data, + " subject alt name(s) and/or common name do not match \"%s\"", + connssl->dispname); + return CURLE_OK; + } +#endif + } +#if LIBWOLFSSL_VERSION_HEX >= 0x02007000 /* 2.7.0 */ + else if(ASN_NO_SIGNER_E == detail) { + if(conn_config->verifypeer) { + failf(data, " CA signer not available for verification"); + return CURLE_SSL_CACERT_BADFILE; + } + else { + /* Just continue with a warning if no strict certificate + verification is required. */ + infof(data, "CA signer not available for verification, " + "continuing anyway"); + } + } +#endif + else if(backend->io_result == CURLE_AGAIN) { + return CURLE_OK; + } + else { + failf(data, "SSL_connect failed with error %d: %s", detail, + wolfSSL_ERR_error_string(detail, error_buffer)); + return CURLE_SSL_CONNECT_ERROR; + } + } + + if(pinnedpubkey) { +#ifdef KEEP_PEER_CERT + X509 *x509; + const char *x509_der; + int x509_der_len; + struct Curl_X509certificate x509_parsed; + struct Curl_asn1Element *pubkey; + CURLcode result; + + x509 = wolfSSL_get_peer_certificate(backend->handle); + if(!x509) { + failf(data, "SSL: failed retrieving server certificate"); + return CURLE_SSL_PINNEDPUBKEYNOTMATCH; + } + + x509_der = (const char *)wolfSSL_X509_get_der(x509, &x509_der_len); + if(!x509_der) { + failf(data, "SSL: failed retrieving ASN.1 server certificate"); + return CURLE_SSL_PINNEDPUBKEYNOTMATCH; + } + + memset(&x509_parsed, 0, sizeof(x509_parsed)); + if(Curl_parseX509(&x509_parsed, x509_der, x509_der + x509_der_len)) + return CURLE_SSL_PINNEDPUBKEYNOTMATCH; + + pubkey = &x509_parsed.subjectPublicKeyInfo; + if(!pubkey->header || pubkey->end <= pubkey->header) { + failf(data, "SSL: failed retrieving public key from server certificate"); + return CURLE_SSL_PINNEDPUBKEYNOTMATCH; + } + + result = Curl_pin_peer_pubkey(data, + pinnedpubkey, + (const unsigned char *)pubkey->header, + (size_t)(pubkey->end - pubkey->header)); + if(result) { + failf(data, "SSL: public key does not match pinned public key"); + return result; + } +#else + failf(data, "Library lacks pinning support built-in"); + return CURLE_NOT_BUILT_IN; +#endif + } + +#ifdef HAVE_ALPN + if(connssl->alpn) { + int rc; + char *protocol = NULL; + unsigned short protocol_len = 0; + + rc = wolfSSL_ALPN_GetProtocol(backend->handle, &protocol, &protocol_len); + + if(rc == SSL_SUCCESS) { + Curl_alpn_set_negotiated(cf, data, (const unsigned char *)protocol, + protocol_len); + } + else if(rc == SSL_ALPN_NOT_FOUND) + Curl_alpn_set_negotiated(cf, data, NULL, 0); + else { + failf(data, "ALPN, failure getting protocol, error %d", rc); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif /* HAVE_ALPN */ + + connssl->connecting_state = ssl_connect_3; +#if (LIBWOLFSSL_VERSION_HEX >= 0x03009010) + infof(data, "SSL connection using %s / %s", + wolfSSL_get_version(backend->handle), + wolfSSL_get_cipher_name(backend->handle)); +#else + infof(data, "SSL connected"); +#endif + + return CURLE_OK; +} + + +static CURLcode +wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ssl_backend_data *backend = + (struct wolfssl_ssl_backend_data *)connssl->backend; + const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + + DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + DEBUGASSERT(backend); + + if(ssl_config->primary.sessionid) { + bool incache; + bool added = FALSE; + void *old_ssl_sessionid = NULL; + /* wolfSSL_get1_session allocates memory that has to be freed. */ + WOLFSSL_SESSION *our_ssl_sessionid = wolfSSL_get1_session(backend->handle); + + if(our_ssl_sessionid) { + Curl_ssl_sessionid_lock(data); + incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)); + if(incache) { + if(old_ssl_sessionid != our_ssl_sessionid) { + infof(data, "old SSL session ID is stale, removing"); + Curl_ssl_delsessionid(data, old_ssl_sessionid); + incache = FALSE; + } + } + + if(!incache) { + result = Curl_ssl_addsessionid(cf, data, our_ssl_sessionid, 0, NULL); + if(result) { + Curl_ssl_sessionid_unlock(data); + wolfSSL_SESSION_free(our_ssl_sessionid); + failf(data, "failed to store ssl session"); + return result; + } + else { + added = TRUE; + } + } + Curl_ssl_sessionid_unlock(data); + + if(!added) { + /* If the session info wasn't added to the cache, free our copy. */ + wolfSSL_SESSION_free(our_ssl_sessionid); + } + } + } + + connssl->connecting_state = ssl_connect_done; + + return result; +} + + +static ssize_t wolfssl_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, + size_t len, + CURLcode *curlcode) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ssl_backend_data *backend = + (struct wolfssl_ssl_backend_data *)connssl->backend; + char error_buffer[WOLFSSL_MAX_ERROR_SZ]; + int memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; + int rc; + + DEBUGASSERT(backend); + + wolfSSL_ERR_clear_error(); + + rc = wolfSSL_write(backend->handle, mem, memlen); + if(rc <= 0) { + int err = wolfSSL_get_error(backend->handle, rc); + + switch(err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* there's data pending, re-invoke SSL_write() */ + CURL_TRC_CF(data, cf, "wolfssl_send(len=%zu) -> AGAIN", len); + *curlcode = CURLE_AGAIN; + return -1; + default: + if(backend->io_result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "wolfssl_send(len=%zu) -> AGAIN", len); + *curlcode = CURLE_AGAIN; + return -1; + } + CURL_TRC_CF(data, cf, "wolfssl_send(len=%zu) -> %d, %d", len, rc, err); + failf(data, "SSL write: %s, errno %d", + wolfSSL_ERR_error_string(err, error_buffer), + SOCKERRNO); + *curlcode = CURLE_SEND_ERROR; + return -1; + } + } + CURL_TRC_CF(data, cf, "wolfssl_send(len=%zu) -> %d", len, rc); + return rc; +} + +static void wolfssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ssl_backend_data *backend = + (struct wolfssl_ssl_backend_data *)connssl->backend; + + (void) data; + + DEBUGASSERT(backend); + + if(backend->handle) { + char buf[32]; + /* Maybe the server has already sent a close notify alert. + Read it to avoid an RST on the TCP connection. */ + (void)wolfSSL_read(backend->handle, buf, (int)sizeof(buf)); + (void)wolfSSL_shutdown(backend->handle); + wolfSSL_free(backend->handle); + backend->handle = NULL; + } + if(backend->ctx) { + wolfSSL_CTX_free(backend->ctx); + backend->ctx = NULL; + } +} + +static ssize_t wolfssl_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t blen, + CURLcode *curlcode) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ssl_backend_data *backend = + (struct wolfssl_ssl_backend_data *)connssl->backend; + char error_buffer[WOLFSSL_MAX_ERROR_SZ]; + int buffsize = (blen > (size_t)INT_MAX) ? INT_MAX : (int)blen; + int nread; + + DEBUGASSERT(backend); + + wolfSSL_ERR_clear_error(); + *curlcode = CURLE_OK; + + nread = wolfSSL_read(backend->handle, buf, buffsize); + + if(nread <= 0) { + int err = wolfSSL_get_error(backend->handle, nread); + + switch(err) { + case SSL_ERROR_ZERO_RETURN: /* no more data */ + CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> CLOSED", blen); + *curlcode = CURLE_OK; + return 0; + case SSL_ERROR_NONE: + /* FALLTHROUGH */ + case SSL_ERROR_WANT_READ: + /* FALLTHROUGH */ + case SSL_ERROR_WANT_WRITE: + /* there's data pending, re-invoke wolfSSL_read() */ + CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> AGAIN", blen); + *curlcode = CURLE_AGAIN; + return -1; + default: + if(backend->io_result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> AGAIN", blen); + *curlcode = CURLE_AGAIN; + return -1; + } + failf(data, "SSL read: %s, errno %d", + wolfSSL_ERR_error_string(err, error_buffer), SOCKERRNO); + *curlcode = CURLE_RECV_ERROR; + return -1; + } + } + CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> %d", blen, nread); + return nread; +} + + +static void wolfssl_session_free(void *ptr) +{ + wolfSSL_SESSION_free(ptr); +} + + +static size_t wolfssl_version(char *buffer, size_t size) +{ +#if LIBWOLFSSL_VERSION_HEX >= 0x03006000 + return msnprintf(buffer, size, "wolfSSL/%s", wolfSSL_lib_version()); +#elif defined(WOLFSSL_VERSION) + return msnprintf(buffer, size, "wolfSSL/%s", WOLFSSL_VERSION); +#endif +} + + +static int wolfssl_init(void) +{ + int ret; + +#ifdef OPENSSL_EXTRA + Curl_tls_keylog_open(); +#endif + ret = (wolfSSL_Init() == SSL_SUCCESS); + wolfssl_bio_cf_init_methods(); + return ret; +} + + +static void wolfssl_cleanup(void) +{ + wolfssl_bio_cf_free_methods(); + wolfSSL_Cleanup(); +#ifdef OPENSSL_EXTRA + Curl_tls_keylog_close(); +#endif +} + + +static bool wolfssl_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct ssl_connect_data *ctx = cf->ctx; + struct wolfssl_ssl_backend_data *backend; + + (void)data; + DEBUGASSERT(ctx && ctx->backend); + + backend = (struct wolfssl_ssl_backend_data *)ctx->backend; + if(backend->handle) /* SSL is in use */ + return (0 != wolfSSL_pending(backend->handle)) ? TRUE : FALSE; + else + return FALSE; +} + + +/* + * This function is called to shut down the SSL layer but keep the + * socket open (CCC - Clear Command Channel) + */ +static int wolfssl_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *ctx = cf->ctx; + struct wolfssl_ssl_backend_data *backend; + int retval = 0; + + (void)data; + DEBUGASSERT(ctx && ctx->backend); + + backend = (struct wolfssl_ssl_backend_data *)ctx->backend; + if(backend->handle) { + wolfSSL_ERR_clear_error(); + wolfSSL_free(backend->handle); + backend->handle = NULL; + } + return retval; +} + + +static CURLcode +wolfssl_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool nonblocking, + bool *done) +{ + CURLcode result; + struct ssl_connect_data *connssl = cf->ctx; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + int what; + + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + if(ssl_connect_1 == connssl->connecting_state) { + /* Find out how much more time we're allowed */ + const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + result = wolfssl_connect_step1(cf, data); + if(result) + return result; + } + + while(ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + + /* check allowed time left */ + const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + if(connssl->connecting_state == ssl_connect_2_reading + || connssl->connecting_state == ssl_connect_2_writing) { + + curl_socket_t writefd = ssl_connect_2_writing == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, + nonblocking?0:timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) { + *done = FALSE; + return CURLE_OK; + } + else { + /* timeout */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + /* Run transaction, and return to the caller if it failed or if + * this connection is part of a multi handle and this loop would + * execute again. This permits the owner of a multi handle to + * abort a connection attempt before step2 has completed while + * ensuring that a client using select() or epoll() will always + * have a valid fdset to wait on. + */ + result = wolfssl_connect_step2(cf, data); + if(result || (nonblocking && + (ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state))) + return result; + } /* repeat step2 until all transactions are done. */ + + if(ssl_connect_3 == connssl->connecting_state) { + result = wolfssl_connect_step3(cf, data); + if(result) + return result; + } + + if(ssl_connect_done == connssl->connecting_state) { + connssl->state = ssl_connection_complete; + *done = TRUE; + } + else + *done = FALSE; + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + + return CURLE_OK; +} + + +static CURLcode wolfssl_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + return wolfssl_connect_common(cf, data, TRUE, done); +} + + +static CURLcode wolfssl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result; + bool done = FALSE; + + result = wolfssl_connect_common(cf, data, FALSE, &done); + if(result) + return result; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +static CURLcode wolfssl_random(struct Curl_easy *data, + unsigned char *entropy, size_t length) +{ + WC_RNG rng; + (void)data; + if(wc_InitRng(&rng)) + return CURLE_FAILED_INIT; + if(length > UINT_MAX) + return CURLE_FAILED_INIT; + if(wc_RNG_GenerateBlock(&rng, entropy, (unsigned)length)) + return CURLE_FAILED_INIT; + if(wc_FreeRng(&rng)) + return CURLE_FAILED_INIT; + return CURLE_OK; +} + +static CURLcode wolfssl_sha256sum(const unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *sha256sum /* output */, + size_t unused) +{ + wc_Sha256 SHA256pw; + (void)unused; + if(wc_InitSha256(&SHA256pw)) + return CURLE_FAILED_INIT; + wc_Sha256Update(&SHA256pw, tmp, (word32)tmplen); + wc_Sha256Final(&SHA256pw, sha256sum); + return CURLE_OK; +} + +static void *wolfssl_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) +{ + struct wolfssl_ssl_backend_data *backend = + (struct wolfssl_ssl_backend_data *)connssl->backend; + (void)info; + DEBUGASSERT(backend); + return backend->handle; +} + +const struct Curl_ssl Curl_ssl_wolfssl = { + { CURLSSLBACKEND_WOLFSSL, "WolfSSL" }, /* info */ + +#ifdef KEEP_PEER_CERT + SSLSUPP_PINNEDPUBKEY | +#endif +#ifdef USE_BIO_CHAIN + SSLSUPP_HTTPS_PROXY | +#endif + SSLSUPP_CA_PATH | + SSLSUPP_CAINFO_BLOB | + SSLSUPP_SSL_CTX, + + sizeof(struct wolfssl_ssl_backend_data), + + wolfssl_init, /* init */ + wolfssl_cleanup, /* cleanup */ + wolfssl_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + wolfssl_shutdown, /* shutdown */ + wolfssl_data_pending, /* data_pending */ + wolfssl_random, /* random */ + Curl_none_cert_status_request, /* cert_status_request */ + wolfssl_connect, /* connect */ + wolfssl_connect_nonblocking, /* connect_nonblocking */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ + wolfssl_get_internals, /* get_internals */ + wolfssl_close, /* close_one */ + Curl_none_close_all, /* close_all */ + wolfssl_session_free, /* session_free */ + Curl_none_set_engine, /* set_engine */ + Curl_none_set_engine_default, /* set_engine_default */ + Curl_none_engines_list, /* engines_list */ + Curl_none_false_start, /* false_start */ + wolfssl_sha256sum, /* sha256sum */ + NULL, /* associate_connection */ + NULL, /* disassociate_connection */ + NULL, /* free_multi_ssl_backend_data */ + wolfssl_recv, /* recv decrypted data */ + wolfssl_send, /* send data to encrypt */ +}; + +#endif diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.h b/Utilities/cmcurl/lib/vtls/wolfssl.h new file mode 100644 index 0000000..a5ed848 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/wolfssl.h @@ -0,0 +1,33 @@ +#ifndef HEADER_CURL_WOLFSSL_H +#define HEADER_CURL_WOLFSSL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_WOLFSSL + +extern const struct Curl_ssl Curl_ssl_wolfssl; + +#endif /* USE_WOLFSSL */ +#endif /* HEADER_CURL_WOLFSSL_H */ diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.c b/Utilities/cmcurl/lib/vtls/x509asn1.c new file mode 100644 index 0000000..8b1eed6 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/x509asn1.c @@ -0,0 +1,1438 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ + defined(USE_SCHANNEL) || defined(USE_SECTRANSP) + +#if defined(USE_WOLFSSL) || defined(USE_SCHANNEL) +#define WANT_PARSEX509 /* uses Curl_parseX509() */ +#endif + +#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) +#define WANT_EXTRACT_CERTINFO /* uses Curl_extract_certinfo() */ +#define WANT_PARSEX509 /* ... uses Curl_parseX509() */ +#endif + +#include <curl/curl.h> +#include "urldata.h" +#include "strcase.h" +#include "curl_ctype.h" +#include "hostcheck.h" +#include "vtls/vtls.h" +#include "vtls/vtls_int.h" +#include "sendf.h" +#include "inet_pton.h" +#include "curl_base64.h" +#include "x509asn1.h" +#include "dynbuf.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Constants. + */ + +/* Largest supported ASN.1 structure. */ +#define CURL_ASN1_MAX ((size_t) 0x40000) /* 256K */ + +/* ASN.1 classes. */ +#define CURL_ASN1_UNIVERSAL 0 +#define CURL_ASN1_APPLICATION 1 +#define CURL_ASN1_CONTEXT_SPECIFIC 2 +#define CURL_ASN1_PRIVATE 3 + +/* ASN.1 types. */ +#define CURL_ASN1_BOOLEAN 1 +#define CURL_ASN1_INTEGER 2 +#define CURL_ASN1_BIT_STRING 3 +#define CURL_ASN1_OCTET_STRING 4 +#define CURL_ASN1_NULL 5 +#define CURL_ASN1_OBJECT_IDENTIFIER 6 +#define CURL_ASN1_OBJECT_DESCRIPTOR 7 +#define CURL_ASN1_INSTANCE_OF 8 +#define CURL_ASN1_REAL 9 +#define CURL_ASN1_ENUMERATED 10 +#define CURL_ASN1_EMBEDDED 11 +#define CURL_ASN1_UTF8_STRING 12 +#define CURL_ASN1_RELATIVE_OID 13 +#define CURL_ASN1_SEQUENCE 16 +#define CURL_ASN1_SET 17 +#define CURL_ASN1_NUMERIC_STRING 18 +#define CURL_ASN1_PRINTABLE_STRING 19 +#define CURL_ASN1_TELETEX_STRING 20 +#define CURL_ASN1_VIDEOTEX_STRING 21 +#define CURL_ASN1_IA5_STRING 22 +#define CURL_ASN1_UTC_TIME 23 +#define CURL_ASN1_GENERALIZED_TIME 24 +#define CURL_ASN1_GRAPHIC_STRING 25 +#define CURL_ASN1_VISIBLE_STRING 26 +#define CURL_ASN1_GENERAL_STRING 27 +#define CURL_ASN1_UNIVERSAL_STRING 28 +#define CURL_ASN1_CHARACTER_STRING 29 +#define CURL_ASN1_BMP_STRING 30 + +#ifdef WANT_EXTRACT_CERTINFO +/* ASN.1 OID table entry. */ +struct Curl_OID { + const char *numoid; /* Dotted-numeric OID. */ + const char *textoid; /* OID name. */ +}; + +/* ASN.1 OIDs. */ +static const char cnOID[] = "2.5.4.3"; /* Common name. */ +static const char sanOID[] = "2.5.29.17"; /* Subject alternative name. */ + +static const struct Curl_OID OIDtable[] = { + { "1.2.840.10040.4.1", "dsa" }, + { "1.2.840.10040.4.3", "dsa-with-sha1" }, + { "1.2.840.10045.2.1", "ecPublicKey" }, + { "1.2.840.10045.3.0.1", "c2pnb163v1" }, + { "1.2.840.10045.4.1", "ecdsa-with-SHA1" }, + { "1.2.840.10046.2.1", "dhpublicnumber" }, + { "1.2.840.113549.1.1.1", "rsaEncryption" }, + { "1.2.840.113549.1.1.2", "md2WithRSAEncryption" }, + { "1.2.840.113549.1.1.4", "md5WithRSAEncryption" }, + { "1.2.840.113549.1.1.5", "sha1WithRSAEncryption" }, + { "1.2.840.113549.1.1.10", "RSASSA-PSS" }, + { "1.2.840.113549.1.1.14", "sha224WithRSAEncryption" }, + { "1.2.840.113549.1.1.11", "sha256WithRSAEncryption" }, + { "1.2.840.113549.1.1.12", "sha384WithRSAEncryption" }, + { "1.2.840.113549.1.1.13", "sha512WithRSAEncryption" }, + { "1.2.840.113549.2.2", "md2" }, + { "1.2.840.113549.2.5", "md5" }, + { "1.3.14.3.2.26", "sha1" }, + { cnOID, "CN" }, + { "2.5.4.4", "SN" }, + { "2.5.4.5", "serialNumber" }, + { "2.5.4.6", "C" }, + { "2.5.4.7", "L" }, + { "2.5.4.8", "ST" }, + { "2.5.4.9", "streetAddress" }, + { "2.5.4.10", "O" }, + { "2.5.4.11", "OU" }, + { "2.5.4.12", "title" }, + { "2.5.4.13", "description" }, + { "2.5.4.17", "postalCode" }, + { "2.5.4.41", "name" }, + { "2.5.4.42", "givenName" }, + { "2.5.4.43", "initials" }, + { "2.5.4.44", "generationQualifier" }, + { "2.5.4.45", "X500UniqueIdentifier" }, + { "2.5.4.46", "dnQualifier" }, + { "2.5.4.65", "pseudonym" }, + { "1.2.840.113549.1.9.1", "emailAddress" }, + { "2.5.4.72", "role" }, + { sanOID, "subjectAltName" }, + { "2.5.29.18", "issuerAltName" }, + { "2.5.29.19", "basicConstraints" }, + { "2.16.840.1.101.3.4.2.4", "sha224" }, + { "2.16.840.1.101.3.4.2.1", "sha256" }, + { "2.16.840.1.101.3.4.2.2", "sha384" }, + { "2.16.840.1.101.3.4.2.3", "sha512" }, + { (const char *) NULL, (const char *) NULL } +}; + +#endif /* WANT_EXTRACT_CERTINFO */ + +/* + * Lightweight ASN.1 parser. + * In particular, it does not check for syntactic/lexical errors. + * It is intended to support certificate information gathering for SSL backends + * that offer a mean to get certificates as a whole, but do not supply + * entry points to get particular certificate sub-fields. + * Please note there is no pretension here to rewrite a full SSL library. + */ + +static const char *getASN1Element(struct Curl_asn1Element *elem, + const char *beg, const char *end) + WARN_UNUSED_RESULT; + +static const char *getASN1Element(struct Curl_asn1Element *elem, + const char *beg, const char *end) +{ + unsigned char b; + size_t len; + struct Curl_asn1Element lelem; + + /* Get a single ASN.1 element into `elem', parse ASN.1 string at `beg' + ending at `end'. + Returns a pointer in source string after the parsed element, or NULL + if an error occurs. */ + if(!beg || !end || beg >= end || !*beg || + (size_t)(end - beg) > CURL_ASN1_MAX) + return NULL; + + /* Process header byte. */ + elem->header = beg; + b = (unsigned char) *beg++; + elem->constructed = (b & 0x20) != 0; + elem->class = (b >> 6) & 3; + b &= 0x1F; + if(b == 0x1F) + return NULL; /* Long tag values not supported here. */ + elem->tag = b; + + /* Process length. */ + if(beg >= end) + return NULL; + b = (unsigned char) *beg++; + if(!(b & 0x80)) + len = b; + else if(!(b &= 0x7F)) { + /* Unspecified length. Since we have all the data, we can determine the + effective length by skipping element until an end element is found. */ + if(!elem->constructed) + return NULL; + elem->beg = beg; + while(beg < end && *beg) { + beg = getASN1Element(&lelem, beg, end); + if(!beg) + return NULL; + } + if(beg >= end) + return NULL; + elem->end = beg; + return beg + 1; + } + else if((unsigned)b > (size_t)(end - beg)) + return NULL; /* Does not fit in source. */ + else { + /* Get long length. */ + len = 0; + do { + if(len & 0xFF000000L) + return NULL; /* Lengths > 32 bits are not supported. */ + len = (len << 8) | (unsigned char) *beg++; + } while(--b); + } + if(len > (size_t)(end - beg)) + return NULL; /* Element data does not fit in source. */ + elem->beg = beg; + elem->end = beg + len; + return elem->end; +} + +#ifdef WANT_EXTRACT_CERTINFO + +/* + * Search the null terminated OID or OID identifier in local table. + * Return the table entry pointer or NULL if not found. + */ +static const struct Curl_OID *searchOID(const char *oid) +{ + const struct Curl_OID *op; + for(op = OIDtable; op->numoid; op++) + if(!strcmp(op->numoid, oid) || strcasecompare(op->textoid, oid)) + return op; + + return NULL; +} + +/* + * Convert an ASN.1 Boolean value into its string representation. Return the + * dynamically allocated string, or NULL if source is not an ASN.1 Boolean + * value. + */ + +static const char *bool2str(const char *beg, const char *end) +{ + if(end - beg != 1) + return NULL; + return strdup(*beg? "TRUE": "FALSE"); +} + +/* + * Convert an ASN.1 octet string to a printable string. + * Return the dynamically allocated string, or NULL if an error occurs. + */ +static const char *octet2str(const char *beg, const char *end) +{ + struct dynbuf buf; + CURLcode result; + + Curl_dyn_init(&buf, 3 * CURL_ASN1_MAX + 1); + result = Curl_dyn_addn(&buf, "", 0); + + while(!result && beg < end) + result = Curl_dyn_addf(&buf, "%02x:", (unsigned char) *beg++); + + return Curl_dyn_ptr(&buf); +} + +static const char *bit2str(const char *beg, const char *end) +{ + /* Convert an ASN.1 bit string to a printable string. + Return the dynamically allocated string, or NULL if an error occurs. */ + + if(++beg > end) + return NULL; + return octet2str(beg, end); +} + +/* + * Convert an ASN.1 integer value into its string representation. + * Return the dynamically allocated string, or NULL if source is not an + * ASN.1 integer value. + */ +static const char *int2str(const char *beg, const char *end) +{ + unsigned int val = 0; + size_t n = end - beg; + + if(!n) + return NULL; + + if(n > 4) + return octet2str(beg, end); + + /* Represent integers <= 32-bit as a single value. */ + if(*beg & 0x80) + val = ~val; + + do + val = (val << 8) | *(const unsigned char *) beg++; + while(beg < end); + return curl_maprintf("%s%x", val >= 10? "0x": "", val); +} + +/* + * Perform a lazy conversion from an ASN.1 typed string to UTF8. Allocate the + * destination buffer dynamically. The allocation size will normally be too + * large: this is to avoid buffer overflows. + * Terminate the string with a nul byte and return the converted + * string length. + */ +static ssize_t +utf8asn1str(char **to, int type, const char *from, const char *end) +{ + size_t inlength = end - from; + int size = 1; + size_t outlength; + char *buf; + + *to = NULL; + switch(type) { + case CURL_ASN1_BMP_STRING: + size = 2; + break; + case CURL_ASN1_UNIVERSAL_STRING: + size = 4; + break; + case CURL_ASN1_NUMERIC_STRING: + case CURL_ASN1_PRINTABLE_STRING: + case CURL_ASN1_TELETEX_STRING: + case CURL_ASN1_IA5_STRING: + case CURL_ASN1_VISIBLE_STRING: + case CURL_ASN1_UTF8_STRING: + break; + default: + return -1; /* Conversion not supported. */ + } + + if(inlength % size) + return -1; /* Length inconsistent with character size. */ + if(inlength / size > (SIZE_T_MAX - 1) / 4) + return -1; /* Too big. */ + buf = malloc(4 * (inlength / size) + 1); + if(!buf) + return -1; /* Not enough memory. */ + + if(type == CURL_ASN1_UTF8_STRING) { + /* Just copy. */ + outlength = inlength; + if(outlength) + memcpy(buf, from, outlength); + } + else { + for(outlength = 0; from < end;) { + int charsize; + unsigned int wc; + + wc = 0; + switch(size) { + case 4: + wc = (wc << 8) | *(const unsigned char *) from++; + wc = (wc << 8) | *(const unsigned char *) from++; + /* FALLTHROUGH */ + case 2: + wc = (wc << 8) | *(const unsigned char *) from++; + /* FALLTHROUGH */ + default: /* case 1: */ + wc = (wc << 8) | *(const unsigned char *) from++; + } + charsize = 1; + if(wc >= 0x00000080) { + if(wc >= 0x00000800) { + if(wc >= 0x00010000) { + if(wc >= 0x00200000) { + free(buf); + return -1; /* Invalid char. size for target encoding. */ + } + buf[outlength + 3] = (char) (0x80 | (wc & 0x3F)); + wc = (wc >> 6) | 0x00010000; + charsize++; + } + buf[outlength + 2] = (char) (0x80 | (wc & 0x3F)); + wc = (wc >> 6) | 0x00000800; + charsize++; + } + buf[outlength + 1] = (char) (0x80 | (wc & 0x3F)); + wc = (wc >> 6) | 0x000000C0; + charsize++; + } + buf[outlength] = (char) wc; + outlength += charsize; + } + } + buf[outlength] = '\0'; + *to = buf; + return outlength; +} + +/* + * Convert an ASN.1 String into its UTF-8 string representation. + * Return the dynamically allocated string, or NULL if an error occurs. + */ +static const char *string2str(int type, const char *beg, const char *end) +{ + char *buf; + if(utf8asn1str(&buf, type, beg, end) < 0) + return NULL; + return buf; +} + +/* + * Decimal ASCII encode unsigned integer `x' into the buflen sized buffer at + * buf. Return the total number of encoded digits, even if larger than + * `buflen'. + */ +static size_t encodeUint(char *buf, size_t buflen, unsigned int x) +{ + size_t i = 0; + unsigned int y = x / 10; + + if(y) { + i = encodeUint(buf, buflen, y); + x -= y * 10; + } + if(i < buflen) + buf[i] = (char) ('0' + x); + i++; + if(i < buflen) + buf[i] = '\0'; /* Store a terminator if possible. */ + return i; +} + +/* + * Convert an ASN.1 OID into its dotted string representation. + * Store the result in th `n'-byte buffer at `buf'. + * Return the converted string length, or 0 on errors. + */ +static size_t encodeOID(char *buf, size_t buflen, + const char *beg, const char *end) +{ + size_t i; + unsigned int x; + unsigned int y; + + /* Process the first two numbers. */ + y = *(const unsigned char *) beg++; + x = y / 40; + y -= x * 40; + i = encodeUint(buf, buflen, x); + if(i < buflen) + buf[i] = '.'; + i++; + if(i >= buflen) + i += encodeUint(NULL, 0, y); + else + i += encodeUint(buf + i, buflen - i, y); + + /* Process the trailing numbers. */ + while(beg < end) { + if(i < buflen) + buf[i] = '.'; + i++; + x = 0; + do { + if(x & 0xFF000000) + return 0; + y = *(const unsigned char *) beg++; + x = (x << 7) | (y & 0x7F); + } while(y & 0x80); + if(i >= buflen) + i += encodeUint(NULL, 0, x); + else + i += encodeUint(buf + i, buflen - i, x); + } + if(i < buflen) + buf[i] = '\0'; + return i; +} + +/* + * Convert an ASN.1 OID into its dotted or symbolic string representation. + * Return the dynamically allocated string, or NULL if an error occurs. + */ + +static const char *OID2str(const char *beg, const char *end, bool symbolic) +{ + char *buf = NULL; + if(beg < end) { + size_t buflen = encodeOID(NULL, 0, beg, end); + if(buflen) { + buf = malloc(buflen + 1); /* one extra for the zero byte */ + if(buf) { + encodeOID(buf, buflen, beg, end); + buf[buflen] = '\0'; + + if(symbolic) { + const struct Curl_OID *op = searchOID(buf); + if(op) { + free(buf); + buf = strdup(op->textoid); + } + } + } + } + } + return buf; +} + +static const char *GTime2str(const char *beg, const char *end) +{ + const char *tzp; + const char *fracp; + char sec1, sec2; + size_t fracl; + size_t tzl; + const char *sep = ""; + + /* Convert an ASN.1 Generalized time to a printable string. + Return the dynamically allocated string, or NULL if an error occurs. */ + + for(fracp = beg; fracp < end && *fracp >= '0' && *fracp <= '9'; fracp++) + ; + + /* Get seconds digits. */ + sec1 = '0'; + switch(fracp - beg - 12) { + case 0: + sec2 = '0'; + break; + case 2: + sec1 = fracp[-2]; + /* FALLTHROUGH */ + case 1: + sec2 = fracp[-1]; + break; + default: + return NULL; + } + + /* Scan for timezone, measure fractional seconds. */ + tzp = fracp; + fracl = 0; + if(fracp < end && (*fracp == '.' || *fracp == ',')) { + fracp++; + do + tzp++; + while(tzp < end && *tzp >= '0' && *tzp <= '9'); + /* Strip leading zeroes in fractional seconds. */ + for(fracl = tzp - fracp - 1; fracl && fracp[fracl - 1] == '0'; fracl--) + ; + } + + /* Process timezone. */ + if(tzp >= end) + ; /* Nothing to do. */ + else if(*tzp == 'Z') { + tzp = " GMT"; + end = tzp + 4; + } + else { + sep = " "; + tzp++; + } + + tzl = end - tzp; + return curl_maprintf("%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s", + beg, beg + 4, beg + 6, + beg + 8, beg + 10, sec1, sec2, + fracl? ".": "", (int)fracl, fracp, + sep, (int)tzl, tzp); +} + +/* + * Convert an ASN.1 UTC time to a printable string. + * Return the dynamically allocated string, or NULL if an error occurs. + */ +static const char *UTime2str(const char *beg, const char *end) +{ + const char *tzp; + size_t tzl; + const char *sec; + + for(tzp = beg; tzp < end && *tzp >= '0' && *tzp <= '9'; tzp++) + ; + /* Get the seconds. */ + sec = beg + 10; + switch(tzp - sec) { + case 0: + sec = "00"; + case 2: + break; + default: + return NULL; + } + + /* Process timezone. */ + if(tzp >= end) + return NULL; + if(*tzp == 'Z') { + tzp = "GMT"; + end = tzp + 3; + } + else + tzp++; + + tzl = end - tzp; + return curl_maprintf("%u%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s", + 20 - (*beg >= '5'), beg, beg + 2, beg + 4, + beg + 6, beg + 8, sec, + (int)tzl, tzp); +} + +/* + * Convert an ASN.1 element to a printable string. + * Return the dynamically allocated string, or NULL if an error occurs. + */ +static const char *ASN1tostr(struct Curl_asn1Element *elem, int type) +{ + if(elem->constructed) + return NULL; /* No conversion of structured elements. */ + + if(!type) + type = elem->tag; /* Type not forced: use element tag as type. */ + + switch(type) { + case CURL_ASN1_BOOLEAN: + return bool2str(elem->beg, elem->end); + case CURL_ASN1_INTEGER: + case CURL_ASN1_ENUMERATED: + return int2str(elem->beg, elem->end); + case CURL_ASN1_BIT_STRING: + return bit2str(elem->beg, elem->end); + case CURL_ASN1_OCTET_STRING: + return octet2str(elem->beg, elem->end); + case CURL_ASN1_NULL: + return strdup(""); + case CURL_ASN1_OBJECT_IDENTIFIER: + return OID2str(elem->beg, elem->end, TRUE); + case CURL_ASN1_UTC_TIME: + return UTime2str(elem->beg, elem->end); + case CURL_ASN1_GENERALIZED_TIME: + return GTime2str(elem->beg, elem->end); + case CURL_ASN1_UTF8_STRING: + case CURL_ASN1_NUMERIC_STRING: + case CURL_ASN1_PRINTABLE_STRING: + case CURL_ASN1_TELETEX_STRING: + case CURL_ASN1_IA5_STRING: + case CURL_ASN1_VISIBLE_STRING: + case CURL_ASN1_UNIVERSAL_STRING: + case CURL_ASN1_BMP_STRING: + return string2str(type, elem->beg, elem->end); + } + + return NULL; /* Unsupported. */ +} + +/* + * ASCII encode distinguished name at `dn' into the `buflen'-sized buffer at + * `buf'. + * + * Returns the total string length, even if larger than `buflen' or -1 on + * error. + */ +static ssize_t encodeDN(char *buf, size_t buflen, struct Curl_asn1Element *dn) +{ + struct Curl_asn1Element rdn; + struct Curl_asn1Element atv; + struct Curl_asn1Element oid; + struct Curl_asn1Element value; + size_t l = 0; + const char *p1; + const char *p2; + const char *p3; + const char *str; + + for(p1 = dn->beg; p1 < dn->end;) { + p1 = getASN1Element(&rdn, p1, dn->end); + if(!p1) + return -1; + for(p2 = rdn.beg; p2 < rdn.end;) { + p2 = getASN1Element(&atv, p2, rdn.end); + if(!p2) + return -1; + p3 = getASN1Element(&oid, atv.beg, atv.end); + if(!p3) + return -1; + if(!getASN1Element(&value, p3, atv.end)) + return -1; + str = ASN1tostr(&oid, 0); + if(!str) + return -1; + + /* Encode delimiter. + If attribute has a short uppercase name, delimiter is ", ". */ + if(l) { + for(p3 = str; ISUPPER(*p3); p3++) + ; + for(p3 = (*p3 || p3 - str > 2)? "/": ", "; *p3; p3++) { + if(l < buflen) + buf[l] = *p3; + l++; + } + } + + /* Encode attribute name. */ + for(p3 = str; *p3; p3++) { + if(l < buflen) + buf[l] = *p3; + l++; + } + free((char *) str); + + /* Generate equal sign. */ + if(l < buflen) + buf[l] = '='; + l++; + + /* Generate value. */ + str = ASN1tostr(&value, 0); + if(!str) + return -1; + for(p3 = str; *p3; p3++) { + if(l < buflen) + buf[l] = *p3; + l++; + } + free((char *) str); + } + } + + return l; +} + +#endif /* WANT_EXTRACT_CERTINFO */ + +#ifdef WANT_PARSEX509 +/* + * ASN.1 parse an X509 certificate into structure subfields. + * Syntax is assumed to have already been checked by the SSL backend. + * See RFC 5280. + */ +int Curl_parseX509(struct Curl_X509certificate *cert, + const char *beg, const char *end) +{ + struct Curl_asn1Element elem; + struct Curl_asn1Element tbsCertificate; + const char *ccp; + static const char defaultVersion = 0; /* v1. */ + + cert->certificate.header = NULL; + cert->certificate.beg = beg; + cert->certificate.end = end; + + /* Get the sequence content. */ + if(!getASN1Element(&elem, beg, end)) + return -1; /* Invalid bounds/size. */ + beg = elem.beg; + end = elem.end; + + /* Get tbsCertificate. */ + beg = getASN1Element(&tbsCertificate, beg, end); + if(!beg) + return -1; + /* Skip the signatureAlgorithm. */ + beg = getASN1Element(&cert->signatureAlgorithm, beg, end); + if(!beg) + return -1; + /* Get the signatureValue. */ + if(!getASN1Element(&cert->signature, beg, end)) + return -1; + + /* Parse TBSCertificate. */ + beg = tbsCertificate.beg; + end = tbsCertificate.end; + /* Get optional version, get serialNumber. */ + cert->version.header = NULL; + cert->version.beg = &defaultVersion; + cert->version.end = &defaultVersion + sizeof(defaultVersion); + beg = getASN1Element(&elem, beg, end); + if(!beg) + return -1; + if(elem.tag == 0) { + if(!getASN1Element(&cert->version, elem.beg, elem.end)) + return -1; + beg = getASN1Element(&elem, beg, end); + if(!beg) + return -1; + } + cert->serialNumber = elem; + /* Get signature algorithm. */ + beg = getASN1Element(&cert->signatureAlgorithm, beg, end); + /* Get issuer. */ + beg = getASN1Element(&cert->issuer, beg, end); + if(!beg) + return -1; + /* Get notBefore and notAfter. */ + beg = getASN1Element(&elem, beg, end); + if(!beg) + return -1; + ccp = getASN1Element(&cert->notBefore, elem.beg, elem.end); + if(!ccp) + return -1; + if(!getASN1Element(&cert->notAfter, ccp, elem.end)) + return -1; + /* Get subject. */ + beg = getASN1Element(&cert->subject, beg, end); + if(!beg) + return -1; + /* Get subjectPublicKeyAlgorithm and subjectPublicKey. */ + beg = getASN1Element(&cert->subjectPublicKeyInfo, beg, end); + if(!beg) + return -1; + ccp = getASN1Element(&cert->subjectPublicKeyAlgorithm, + cert->subjectPublicKeyInfo.beg, + cert->subjectPublicKeyInfo.end); + if(!ccp) + return -1; + if(!getASN1Element(&cert->subjectPublicKey, ccp, + cert->subjectPublicKeyInfo.end)) + return -1; + /* Get optional issuerUiqueID, subjectUniqueID and extensions. */ + cert->issuerUniqueID.tag = cert->subjectUniqueID.tag = 0; + cert->extensions.tag = elem.tag = 0; + cert->issuerUniqueID.header = cert->subjectUniqueID.header = NULL; + cert->issuerUniqueID.beg = cert->issuerUniqueID.end = ""; + cert->subjectUniqueID.beg = cert->subjectUniqueID.end = ""; + cert->extensions.header = NULL; + cert->extensions.beg = cert->extensions.end = ""; + if(beg < end) { + beg = getASN1Element(&elem, beg, end); + if(!beg) + return -1; + } + if(elem.tag == 1) { + cert->issuerUniqueID = elem; + if(beg < end) { + beg = getASN1Element(&elem, beg, end); + if(!beg) + return -1; + } + } + if(elem.tag == 2) { + cert->subjectUniqueID = elem; + if(beg < end) { + beg = getASN1Element(&elem, beg, end); + if(!beg) + return -1; + } + } + if(elem.tag == 3) + if(!getASN1Element(&cert->extensions, elem.beg, elem.end)) + return -1; + return 0; +} + +#endif /* WANT_PARSEX509 */ + +#ifdef WANT_EXTRACT_CERTINFO + +/* + * Copy at most 64-characters, terminate with a newline and returns the + * effective number of stored characters. + */ +static size_t copySubstring(char *to, const char *from) +{ + size_t i; + for(i = 0; i < 64; i++) { + to[i] = *from; + if(!*from++) + break; + } + + to[i++] = '\n'; + return i; +} + +static const char *dumpAlgo(struct Curl_asn1Element *param, + const char *beg, const char *end) +{ + struct Curl_asn1Element oid; + + /* Get algorithm parameters and return algorithm name. */ + + beg = getASN1Element(&oid, beg, end); + if(!beg) + return NULL; + param->header = NULL; + param->tag = 0; + param->beg = param->end = end; + if(beg < end) + if(!getASN1Element(param, beg, end)) + return NULL; + return OID2str(oid.beg, oid.end, TRUE); +} + +/* + * This is a convenience function for push_certinfo_len that takes a zero + * terminated value. + */ +static CURLcode ssl_push_certinfo(struct Curl_easy *data, + int certnum, + const char *label, + const char *value) +{ + size_t valuelen = strlen(value); + + return Curl_ssl_push_certinfo_len(data, certnum, label, value, valuelen); +} + +/* return 0 on success, 1 on error */ +static int do_pubkey_field(struct Curl_easy *data, int certnum, + const char *label, struct Curl_asn1Element *elem) +{ + const char *output; + CURLcode result = CURLE_OK; + + /* Generate a certificate information record for the public key. */ + + output = ASN1tostr(elem, 0); + if(output) { + if(data->set.ssl.certinfo) + result = ssl_push_certinfo(data, certnum, label, output); + if(!certnum && !result) + infof(data, " %s: %s", label, output); + free((char *) output); + } + return result ? 1 : 0; +} + +/* return 0 on success, 1 on error */ +static int do_pubkey(struct Curl_easy *data, int certnum, + const char *algo, struct Curl_asn1Element *param, + struct Curl_asn1Element *pubkey) +{ + struct Curl_asn1Element elem; + struct Curl_asn1Element pk; + const char *p; + + /* Generate all information records for the public key. */ + + if(strcasecompare(algo, "ecPublicKey")) { + /* + * ECC public key is all the data, a value of type BIT STRING mapped to + * OCTET STRING and should not be parsed as an ASN.1 value. + */ + const size_t len = ((pubkey->end - pubkey->beg - 2) * 4); + if(!certnum) + infof(data, " ECC Public Key (%lu bits)", len); + if(data->set.ssl.certinfo) { + char q[sizeof(len) * 8 / 3 + 1]; + (void)msnprintf(q, sizeof(q), "%zu", len); + if(ssl_push_certinfo(data, certnum, "ECC Public Key", q)) + return 1; + } + return do_pubkey_field(data, certnum, "ecPublicKey", pubkey); + } + + /* Get the public key (single element). */ + if(!getASN1Element(&pk, pubkey->beg + 1, pubkey->end)) + return 1; + + if(strcasecompare(algo, "rsaEncryption")) { + const char *q; + size_t len; + + p = getASN1Element(&elem, pk.beg, pk.end); + if(!p) + return 1; + + /* Compute key length. */ + for(q = elem.beg; !*q && q < elem.end; q++) + ; + len = ((elem.end - q) * 8); + if(len) { + unsigned int i; + for(i = *(unsigned char *) q; !(i & 0x80); i <<= 1) + len--; + } + if(len > 32) + elem.beg = q; /* Strip leading zero bytes. */ + if(!certnum) + infof(data, " RSA Public Key (%lu bits)", len); + if(data->set.ssl.certinfo) { + char r[sizeof(len) * 8 / 3 + 1]; + msnprintf(r, sizeof(r), "%zu", len); + if(ssl_push_certinfo(data, certnum, "RSA Public Key", r)) + return 1; + } + /* Generate coefficients. */ + if(do_pubkey_field(data, certnum, "rsa(n)", &elem)) + return 1; + if(!getASN1Element(&elem, p, pk.end)) + return 1; + if(do_pubkey_field(data, certnum, "rsa(e)", &elem)) + return 1; + } + else if(strcasecompare(algo, "dsa")) { + p = getASN1Element(&elem, param->beg, param->end); + if(p) { + if(do_pubkey_field(data, certnum, "dsa(p)", &elem)) + return 1; + p = getASN1Element(&elem, p, param->end); + if(p) { + if(do_pubkey_field(data, certnum, "dsa(q)", &elem)) + return 1; + if(getASN1Element(&elem, p, param->end)) { + if(do_pubkey_field(data, certnum, "dsa(g)", &elem)) + return 1; + if(do_pubkey_field(data, certnum, "dsa(pub_key)", &pk)) + return 1; + } + } + } + } + else if(strcasecompare(algo, "dhpublicnumber")) { + p = getASN1Element(&elem, param->beg, param->end); + if(p) { + if(do_pubkey_field(data, certnum, "dh(p)", &elem)) + return 1; + if(getASN1Element(&elem, param->beg, param->end)) { + if(do_pubkey_field(data, certnum, "dh(g)", &elem)) + return 1; + if(do_pubkey_field(data, certnum, "dh(pub_key)", &pk)) + return 1; + } + } + } + return 0; +} + +/* + * Convert an ASN.1 distinguished name into a printable string. + * Return the dynamically allocated string, or NULL if an error occurs. + */ +static const char *DNtostr(struct Curl_asn1Element *dn) +{ + char *buf = NULL; + ssize_t buflen = encodeDN(NULL, 0, dn); + + if(buflen >= 0) { + buf = malloc(buflen + 1); + if(buf) { + if(encodeDN(buf, buflen + 1, dn) == -1) { + free(buf); + return NULL; + } + buf[buflen] = '\0'; + } + } + return buf; +} + +CURLcode Curl_extract_certinfo(struct Curl_easy *data, + int certnum, + const char *beg, + const char *end) +{ + struct Curl_X509certificate cert; + struct Curl_asn1Element param; + const char *ccp; + char *cp1; + size_t cl1; + char *cp2; + CURLcode result = CURLE_OK; + unsigned int version; + size_t i; + size_t j; + + if(!data->set.ssl.certinfo) + if(certnum) + return CURLE_OK; + + /* Prepare the certificate information for curl_easy_getinfo(). */ + + /* Extract the certificate ASN.1 elements. */ + if(Curl_parseX509(&cert, beg, end)) + return CURLE_PEER_FAILED_VERIFICATION; + + /* Subject. */ + ccp = DNtostr(&cert.subject); + if(!ccp) + return CURLE_OUT_OF_MEMORY; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo(data, certnum, "Subject", ccp); + if(result) + return result; + } + if(!certnum) + infof(data, "%2d Subject: %s", certnum, ccp); + free((char *) ccp); + + /* Issuer. */ + ccp = DNtostr(&cert.issuer); + if(!ccp) + return CURLE_OUT_OF_MEMORY; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo(data, certnum, "Issuer", ccp); + } + if(!certnum) + infof(data, " Issuer: %s", ccp); + free((char *) ccp); + if(result) + return result; + + /* Version (always fits in less than 32 bits). */ + version = 0; + for(ccp = cert.version.beg; ccp < cert.version.end; ccp++) + version = (version << 8) | *(const unsigned char *) ccp; + if(data->set.ssl.certinfo) { + ccp = curl_maprintf("%x", version); + if(!ccp) + return CURLE_OUT_OF_MEMORY; + result = ssl_push_certinfo(data, certnum, "Version", ccp); + free((char *) ccp); + if(result) + return result; + } + if(!certnum) + infof(data, " Version: %u (0x%x)", version + 1, version); + + /* Serial number. */ + ccp = ASN1tostr(&cert.serialNumber, 0); + if(!ccp) + return CURLE_OUT_OF_MEMORY; + if(data->set.ssl.certinfo) + result = ssl_push_certinfo(data, certnum, "Serial Number", ccp); + if(!certnum) + infof(data, " Serial Number: %s", ccp); + free((char *) ccp); + if(result) + return result; + + /* Signature algorithm .*/ + ccp = dumpAlgo(¶m, cert.signatureAlgorithm.beg, + cert.signatureAlgorithm.end); + if(!ccp) + return CURLE_OUT_OF_MEMORY; + if(data->set.ssl.certinfo) + result = ssl_push_certinfo(data, certnum, "Signature Algorithm", ccp); + if(!certnum) + infof(data, " Signature Algorithm: %s", ccp); + free((char *) ccp); + if(result) + return result; + + /* Start Date. */ + ccp = ASN1tostr(&cert.notBefore, 0); + if(!ccp) + return CURLE_OUT_OF_MEMORY; + if(data->set.ssl.certinfo) + result = ssl_push_certinfo(data, certnum, "Start Date", ccp); + if(!certnum) + infof(data, " Start Date: %s", ccp); + free((char *) ccp); + if(result) + return result; + + /* Expire Date. */ + ccp = ASN1tostr(&cert.notAfter, 0); + if(!ccp) + return CURLE_OUT_OF_MEMORY; + if(data->set.ssl.certinfo) + result = ssl_push_certinfo(data, certnum, "Expire Date", ccp); + if(!certnum) + infof(data, " Expire Date: %s", ccp); + free((char *) ccp); + if(result) + return result; + + /* Public Key Algorithm. */ + ccp = dumpAlgo(¶m, cert.subjectPublicKeyAlgorithm.beg, + cert.subjectPublicKeyAlgorithm.end); + if(!ccp) + return CURLE_OUT_OF_MEMORY; + if(data->set.ssl.certinfo) + result = ssl_push_certinfo(data, certnum, "Public Key Algorithm", + ccp); + if(!result) { + int ret; + if(!certnum) + infof(data, " Public Key Algorithm: %s", ccp); + ret = do_pubkey(data, certnum, ccp, ¶m, &cert.subjectPublicKey); + if(ret) + result = CURLE_OUT_OF_MEMORY; /* the most likely error */ + } + free((char *) ccp); + if(result) + return result; + + /* Signature. */ + ccp = ASN1tostr(&cert.signature, 0); + if(!ccp) + return CURLE_OUT_OF_MEMORY; + if(data->set.ssl.certinfo) + result = ssl_push_certinfo(data, certnum, "Signature", ccp); + if(!certnum) + infof(data, " Signature: %s", ccp); + free((char *) ccp); + if(result) + return result; + + /* Generate PEM certificate. */ + result = Curl_base64_encode(cert.certificate.beg, + cert.certificate.end - cert.certificate.beg, + &cp1, &cl1); + if(result) + return result; + /* Compute the number of characters in final certificate string. Format is: + -----BEGIN CERTIFICATE-----\n + <max 64 base64 characters>\n + . + . + . + -----END CERTIFICATE-----\n + */ + i = 28 + cl1 + (cl1 + 64 - 1) / 64 + 26; + cp2 = malloc(i + 1); + if(!cp2) { + free(cp1); + return CURLE_OUT_OF_MEMORY; + } + /* Build the certificate string. */ + i = copySubstring(cp2, "-----BEGIN CERTIFICATE-----"); + for(j = 0; j < cl1; j += 64) + i += copySubstring(cp2 + i, cp1 + j); + i += copySubstring(cp2 + i, "-----END CERTIFICATE-----"); + cp2[i] = '\0'; + free(cp1); + if(data->set.ssl.certinfo) + result = ssl_push_certinfo(data, certnum, "Cert", cp2); + if(!certnum) + infof(data, "%s", cp2); + free(cp2); + return result; +} + +#endif /* WANT_EXTRACT_CERTINFO */ + +#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */ + +#ifdef WANT_VERIFYHOST + +static const char *checkOID(const char *beg, const char *end, + const char *oid) +{ + struct Curl_asn1Element e; + const char *ccp; + const char *p; + bool matched; + + /* Check if first ASN.1 element at `beg' is the given OID. + Return a pointer in the source after the OID if found, else NULL. */ + + ccp = getASN1Element(&e, beg, end); + if(!ccp || e.tag != CURL_ASN1_OBJECT_IDENTIFIER) + return NULL; + + p = OID2str(e.beg, e.end, FALSE); + if(!p) + return NULL; + + matched = !strcmp(p, oid); + free((char *) p); + return matched? ccp: NULL; +} + +CURLcode Curl_verifyhost(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *beg, const char *end) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_X509certificate cert; + struct Curl_asn1Element dn; + struct Curl_asn1Element elem; + struct Curl_asn1Element ext; + struct Curl_asn1Element name; + const char *p; + const char *q; + char *dnsname; + int matched = -1; + size_t addrlen = (size_t) -1; + ssize_t len; + size_t hostlen; + +#ifdef ENABLE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + + /* Verify that connection server matches info in X509 certificate at + `beg'..`end'. */ + + if(!conn_config->verifyhost) + return CURLE_OK; + + if(Curl_parseX509(&cert, beg, end)) + return CURLE_PEER_FAILED_VERIFICATION; + + hostlen = strlen(connssl->peer.hostname); + + /* Get the server IP address. */ +#ifdef ENABLE_IPV6 + if(cf->conn->bits.ipv6_ip && + Curl_inet_pton(AF_INET6, connssl->peer.hostname, &addr)) + addrlen = sizeof(struct in6_addr); + else +#endif + if(Curl_inet_pton(AF_INET, connssl->peer.hostname, &addr)) + addrlen = sizeof(struct in_addr); + + /* Process extensions. */ + for(p = cert.extensions.beg; p < cert.extensions.end && matched != 1;) { + p = getASN1Element(&ext, p, cert.extensions.end); + if(!p) + return CURLE_PEER_FAILED_VERIFICATION; + + /* Check if extension is a subjectAlternativeName. */ + ext.beg = checkOID(ext.beg, ext.end, sanOID); + if(ext.beg) { + ext.beg = getASN1Element(&elem, ext.beg, ext.end); + if(!ext.beg) + return CURLE_PEER_FAILED_VERIFICATION; + /* Skip critical if present. */ + if(elem.tag == CURL_ASN1_BOOLEAN) { + ext.beg = getASN1Element(&elem, ext.beg, ext.end); + if(!ext.beg) + return CURLE_PEER_FAILED_VERIFICATION; + } + /* Parse the octet string contents: is a single sequence. */ + if(!getASN1Element(&elem, elem.beg, elem.end)) + return CURLE_PEER_FAILED_VERIFICATION; + /* Check all GeneralNames. */ + for(q = elem.beg; matched != 1 && q < elem.end;) { + q = getASN1Element(&name, q, elem.end); + if(!q) + break; + switch(name.tag) { + case 2: /* DNS name. */ + len = utf8asn1str(&dnsname, CURL_ASN1_IA5_STRING, + name.beg, name.end); + if(len > 0 && (size_t)len == strlen(dnsname)) + matched = Curl_cert_hostcheck(dnsname, (size_t)len, + connssl->peer.hostname, hostlen); + else + matched = 0; + free(dnsname); + break; + + case 7: /* IP address. */ + matched = (size_t)(name.end - name.beg) == addrlen && + !memcmp(&addr, name.beg, addrlen); + break; + } + } + } + } + + switch(matched) { + case 1: + /* an alternative name matched the server hostname */ + infof(data, " subjectAltName: %s matched", connssl->dispname); + return CURLE_OK; + case 0: + /* an alternative name field existed, but didn't match and then + we MUST fail */ + infof(data, " subjectAltName does not match %s", connssl->dispname); + return CURLE_PEER_FAILED_VERIFICATION; + } + + /* Process subject. */ + name.header = NULL; + name.beg = name.end = ""; + q = cert.subject.beg; + /* we have to look to the last occurrence of a commonName in the + distinguished one to get the most significant one. */ + while(q < cert.subject.end) { + q = getASN1Element(&dn, q, cert.subject.end); + if(!q) + break; + for(p = dn.beg; p < dn.end;) { + p = getASN1Element(&elem, p, dn.end); + if(!p) + return CURLE_PEER_FAILED_VERIFICATION; + /* We have a DN's AttributeTypeAndValue: check it in case it's a CN. */ + elem.beg = checkOID(elem.beg, elem.end, cnOID); + if(elem.beg) + name = elem; /* Latch CN. */ + } + } + + /* Check the CN if found. */ + if(!getASN1Element(&elem, name.beg, name.end)) + failf(data, "SSL: unable to obtain common name from peer certificate"); + else { + len = utf8asn1str(&dnsname, elem.tag, elem.beg, elem.end); + if(len < 0) { + free(dnsname); + return CURLE_OUT_OF_MEMORY; + } + if(strlen(dnsname) != (size_t) len) /* Nul byte in string ? */ + failf(data, "SSL: illegal cert name field"); + else if(Curl_cert_hostcheck((const char *) dnsname, + len, connssl->peer.hostname, hostlen)) { + infof(data, " common name: %s (matched)", dnsname); + free(dnsname); + return CURLE_OK; + } + else + failf(data, "SSL: certificate subject name '%s' does not match " + "target host name '%s'", dnsname, connssl->dispname); + free(dnsname); + } + + return CURLE_PEER_FAILED_VERIFICATION; +} + +#endif /* WANT_VERIFYHOST */ diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.h b/Utilities/cmcurl/lib/vtls/x509asn1.h new file mode 100644 index 0000000..23a67b8 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/x509asn1.h @@ -0,0 +1,80 @@ +#ifndef HEADER_CURL_X509ASN1_H +#define HEADER_CURL_X509ASN1_H + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ + defined(USE_SCHANNEL) || defined(USE_SECTRANSP) + +#include "cfilters.h" +#include "urldata.h" + +/* + * Types. + */ + +/* ASN.1 parsed element. */ +struct Curl_asn1Element { + const char *header; /* Pointer to header byte. */ + const char *beg; /* Pointer to element data. */ + const char *end; /* Pointer to 1st byte after element. */ + unsigned char class; /* ASN.1 element class. */ + unsigned char tag; /* ASN.1 element tag. */ + bool constructed; /* Element is constructed. */ +}; + +/* X509 certificate: RFC 5280. */ +struct Curl_X509certificate { + struct Curl_asn1Element certificate; + struct Curl_asn1Element version; + struct Curl_asn1Element serialNumber; + struct Curl_asn1Element signatureAlgorithm; + struct Curl_asn1Element signature; + struct Curl_asn1Element issuer; + struct Curl_asn1Element notBefore; + struct Curl_asn1Element notAfter; + struct Curl_asn1Element subject; + struct Curl_asn1Element subjectPublicKeyInfo; + struct Curl_asn1Element subjectPublicKeyAlgorithm; + struct Curl_asn1Element subjectPublicKey; + struct Curl_asn1Element issuerUniqueID; + struct Curl_asn1Element subjectUniqueID; + struct Curl_asn1Element extensions; +}; + +/* + * Prototypes. + */ + +int Curl_parseX509(struct Curl_X509certificate *cert, + const char *beg, const char *end); +CURLcode Curl_extract_certinfo(struct Curl_easy *data, int certnum, + const char *beg, const char *end); +CURLcode Curl_verifyhost(struct Curl_cfilter *cf, struct Curl_easy *data, + const char *beg, const char *end); +#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */ +#endif /* HEADER_CURL_X509ASN1_H */ diff --git a/Utilities/cmcurl/lib/warnless.c b/Utilities/cmcurl/lib/warnless.c new file mode 100644 index 0000000..c80937b --- /dev/null +++ b/Utilities/cmcurl/lib/warnless.c @@ -0,0 +1,386 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(__INTEL_COMPILER) && defined(__unix__) + +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif + +#endif /* __INTEL_COMPILER && __unix__ */ + +#include "warnless.h" + +#ifdef _WIN32 +#undef read +#undef write +#endif + +#include <limits.h> + +#define CURL_MASK_UCHAR ((unsigned char)~0) +#define CURL_MASK_SCHAR (CURL_MASK_UCHAR >> 1) + +#define CURL_MASK_USHORT ((unsigned short)~0) +#define CURL_MASK_SSHORT (CURL_MASK_USHORT >> 1) + +#define CURL_MASK_UINT ((unsigned int)~0) +#define CURL_MASK_SINT (CURL_MASK_UINT >> 1) + +#define CURL_MASK_ULONG ((unsigned long)~0) +#define CURL_MASK_SLONG (CURL_MASK_ULONG >> 1) + +#define CURL_MASK_UCOFFT ((unsigned CURL_TYPEOF_CURL_OFF_T)~0) +#define CURL_MASK_SCOFFT (CURL_MASK_UCOFFT >> 1) + +#define CURL_MASK_USIZE_T ((size_t)~0) +#define CURL_MASK_SSIZE_T (CURL_MASK_USIZE_T >> 1) + +/* +** unsigned long to unsigned short +*/ + +unsigned short curlx_ultous(unsigned long ulnum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_USHORT); + return (unsigned short)(ulnum & (unsigned long) CURL_MASK_USHORT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned long to unsigned char +*/ + +unsigned char curlx_ultouc(unsigned long ulnum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_UCHAR); + return (unsigned char)(ulnum & (unsigned long) CURL_MASK_UCHAR); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned size_t to signed curl_off_t +*/ + +curl_off_t curlx_uztoso(size_t uznum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable:4310) /* cast truncates constant value */ +#endif + + DEBUGASSERT(uznum <= (size_t) CURL_MASK_SCOFFT); + return (curl_off_t)(uznum & (size_t) CURL_MASK_SCOFFT); + +#if defined(__INTEL_COMPILER) || defined(_MSC_VER) +# pragma warning(pop) +#endif +} + +/* +** unsigned size_t to signed int +*/ + +int curlx_uztosi(size_t uznum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(uznum <= (size_t) CURL_MASK_SINT); + return (int)(uznum & (size_t) CURL_MASK_SINT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned size_t to unsigned long +*/ + +unsigned long curlx_uztoul(size_t uznum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + +#if ULONG_MAX < SIZE_T_MAX + DEBUGASSERT(uznum <= (size_t) CURL_MASK_ULONG); +#endif + return (unsigned long)(uznum & (size_t) CURL_MASK_ULONG); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned size_t to unsigned int +*/ + +unsigned int curlx_uztoui(size_t uznum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + +#if UINT_MAX < SIZE_T_MAX + DEBUGASSERT(uznum <= (size_t) CURL_MASK_UINT); +#endif + return (unsigned int)(uznum & (size_t) CURL_MASK_UINT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed long to signed int +*/ + +int curlx_sltosi(long slnum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(slnum >= 0); +#if INT_MAX < LONG_MAX + DEBUGASSERT((unsigned long) slnum <= (unsigned long) CURL_MASK_SINT); +#endif + return (int)(slnum & (long) CURL_MASK_SINT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed long to unsigned int +*/ + +unsigned int curlx_sltoui(long slnum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(slnum >= 0); +#if UINT_MAX < LONG_MAX + DEBUGASSERT((unsigned long) slnum <= (unsigned long) CURL_MASK_UINT); +#endif + return (unsigned int)(slnum & (long) CURL_MASK_UINT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed long to unsigned short +*/ + +unsigned short curlx_sltous(long slnum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(slnum >= 0); + DEBUGASSERT((unsigned long) slnum <= (unsigned long) CURL_MASK_USHORT); + return (unsigned short)(slnum & (long) CURL_MASK_USHORT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned size_t to signed ssize_t +*/ + +ssize_t curlx_uztosz(size_t uznum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(uznum <= (size_t) CURL_MASK_SSIZE_T); + return (ssize_t)(uznum & (size_t) CURL_MASK_SSIZE_T); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed curl_off_t to unsigned size_t +*/ + +size_t curlx_sotouz(curl_off_t sonum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(sonum >= 0); + return (size_t)(sonum & (curl_off_t) CURL_MASK_USIZE_T); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed ssize_t to signed int +*/ + +int curlx_sztosi(ssize_t sznum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(sznum >= 0); +#if INT_MAX < SSIZE_T_MAX + DEBUGASSERT((size_t) sznum <= (size_t) CURL_MASK_SINT); +#endif + return (int)(sznum & (ssize_t) CURL_MASK_SINT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned int to unsigned short +*/ + +unsigned short curlx_uitous(unsigned int uinum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(uinum <= (unsigned int) CURL_MASK_USHORT); + return (unsigned short) (uinum & (unsigned int) CURL_MASK_USHORT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed int to unsigned size_t +*/ + +size_t curlx_sitouz(int sinum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(sinum >= 0); + return (size_t) sinum; + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +#ifdef USE_WINSOCK + +/* +** curl_socket_t to signed int +*/ + +int curlx_sktosi(curl_socket_t s) +{ + return (int)((ssize_t) s); +} + +/* +** signed int to curl_socket_t +*/ + +curl_socket_t curlx_sitosk(int i) +{ + return (curl_socket_t)((ssize_t) i); +} + +#endif /* USE_WINSOCK */ + +#if defined(_WIN32) + +ssize_t curlx_read(int fd, void *buf, size_t count) +{ + return (ssize_t)read(fd, buf, curlx_uztoui(count)); +} + +ssize_t curlx_write(int fd, const void *buf, size_t count) +{ + return (ssize_t)write(fd, buf, curlx_uztoui(count)); +} + +#endif /* _WIN32 */ + +/* Ensure that warnless.h redefinitions continue to have an effect + in "unity" builds. */ +#undef HEADER_CURL_WARNLESS_H_REDEFS diff --git a/Utilities/cmcurl/lib/warnless.h b/Utilities/cmcurl/lib/warnless.h new file mode 100644 index 0000000..e5a02c8 --- /dev/null +++ b/Utilities/cmcurl/lib/warnless.h @@ -0,0 +1,106 @@ +#ifndef HEADER_CURL_WARNLESS_H +#define HEADER_CURL_WARNLESS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_WINSOCK +#include <curl/curl.h> /* for curl_socket_t */ +#endif + +#define CURLX_FUNCTION_CAST(target_type, func) \ + (target_type)(void (*) (void))(func) + +unsigned short curlx_ultous(unsigned long ulnum); + +unsigned char curlx_ultouc(unsigned long ulnum); + +int curlx_uztosi(size_t uznum); + +curl_off_t curlx_uztoso(size_t uznum); + +unsigned long curlx_uztoul(size_t uznum); + +unsigned int curlx_uztoui(size_t uznum); + +int curlx_sltosi(long slnum); + +unsigned int curlx_sltoui(long slnum); + +unsigned short curlx_sltous(long slnum); + +ssize_t curlx_uztosz(size_t uznum); + +size_t curlx_sotouz(curl_off_t sonum); + +int curlx_sztosi(ssize_t sznum); + +unsigned short curlx_uitous(unsigned int uinum); + +size_t curlx_sitouz(int sinum); + +#ifdef USE_WINSOCK + +int curlx_sktosi(curl_socket_t s); + +curl_socket_t curlx_sitosk(int i); + +#endif /* USE_WINSOCK */ + +#if defined(_WIN32) + +ssize_t curlx_read(int fd, void *buf, size_t count); + +ssize_t curlx_write(int fd, const void *buf, size_t count); + +#endif /* _WIN32 */ + +#if defined(__INTEL_COMPILER) && defined(__unix__) + +int curlx_FD_ISSET(int fd, fd_set *fdset); + +void curlx_FD_SET(int fd, fd_set *fdset); + +void curlx_FD_ZERO(fd_set *fdset); + +unsigned short curlx_htons(unsigned short usnum); + +unsigned short curlx_ntohs(unsigned short usnum); + +#endif /* __INTEL_COMPILER && __unix__ */ + +#endif /* HEADER_CURL_WARNLESS_H */ + +#ifndef HEADER_CURL_WARNLESS_H_REDEFS +#define HEADER_CURL_WARNLESS_H_REDEFS + +#if defined(_WIN32) +#undef read +#define read(fd, buf, count) curlx_read(fd, buf, count) +#undef write +#define write(fd, buf, count) curlx_write(fd, buf, count) +#endif + +#endif /* HEADER_CURL_WARNLESS_H_REDEFS */ diff --git a/Utilities/cmcurl/lib/ws.c b/Utilities/cmcurl/lib/ws.c new file mode 100644 index 0000000..adde531 --- /dev/null +++ b/Utilities/cmcurl/lib/ws.c @@ -0,0 +1,1132 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" +#include <curl/curl.h> + +#ifdef USE_WEBSOCKETS + +#include "urldata.h" +#include "bufq.h" +#include "dynbuf.h" +#include "rand.h" +#include "curl_base64.h" +#include "connect.h" +#include "sendf.h" +#include "multiif.h" +#include "ws.h" +#include "easyif.h" +#include "transfer.h" +#include "nonblock.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +#define WSBIT_FIN 0x80 +#define WSBIT_OPCODE_CONT 0 +#define WSBIT_OPCODE_TEXT (1) +#define WSBIT_OPCODE_BIN (2) +#define WSBIT_OPCODE_CLOSE (8) +#define WSBIT_OPCODE_PING (9) +#define WSBIT_OPCODE_PONG (0xa) +#define WSBIT_OPCODE_MASK (0xf) + +#define WSBIT_MASK 0x80 + +/* buffer dimensioning */ +#define WS_CHUNK_SIZE 65535 +#define WS_CHUNK_COUNT 2 + +struct ws_frame_meta { + char proto_opcode; + int flags; + const char *name; +}; + +static struct ws_frame_meta WS_FRAMES[] = { + { WSBIT_OPCODE_CONT, CURLWS_CONT, "CONT" }, + { WSBIT_OPCODE_TEXT, CURLWS_TEXT, "TEXT" }, + { WSBIT_OPCODE_BIN, CURLWS_BINARY, "BIN" }, + { WSBIT_OPCODE_CLOSE, CURLWS_CLOSE, "CLOSE" }, + { WSBIT_OPCODE_PING, CURLWS_PING, "PING" }, + { WSBIT_OPCODE_PONG, CURLWS_PONG, "PONG" }, +}; + +static const char *ws_frame_name_of_op(unsigned char proto_opcode) +{ + unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK; + size_t i; + for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) { + if(WS_FRAMES[i].proto_opcode == opcode) + return WS_FRAMES[i].name; + } + return "???"; +} + +static int ws_frame_op2flags(unsigned char proto_opcode) +{ + unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK; + size_t i; + for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) { + if(WS_FRAMES[i].proto_opcode == opcode) + return WS_FRAMES[i].flags; + } + return 0; +} + +static unsigned char ws_frame_flags2op(int flags) +{ + size_t i; + for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) { + if(WS_FRAMES[i].flags & flags) + return WS_FRAMES[i].proto_opcode; + } + return 0; +} + +static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data, + const char *msg) +{ + switch(dec->head_len) { + case 0: + break; + case 1: + infof(data, "WS-DEC: %s [%s%s]", msg, + ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL"); + break; + default: + if(dec->head_len < dec->head_total) { + infof(data, "WS-DEC: %s [%s%s](%d/%d)", msg, + ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL", + dec->head_len, dec->head_total); + } + else { + infof(data, "WS-DEC: %s [%s%s payload=%" CURL_FORMAT_CURL_OFF_T + "/%" CURL_FORMAT_CURL_OFF_T "]", + msg, ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL", + dec->payload_offset, dec->payload_len); + } + break; + } +} + +typedef ssize_t ws_write_payload(const unsigned char *buf, size_t buflen, + int frame_age, int frame_flags, + curl_off_t payload_offset, + curl_off_t payload_len, + void *userp, + CURLcode *err); + + +static void ws_dec_reset(struct ws_decoder *dec) +{ + dec->frame_age = 0; + dec->frame_flags = 0; + dec->payload_offset = 0; + dec->payload_len = 0; + dec->head_len = dec->head_total = 0; + dec->state = WS_DEC_INIT; +} + +static void ws_dec_init(struct ws_decoder *dec) +{ + ws_dec_reset(dec); +} + +static CURLcode ws_dec_read_head(struct ws_decoder *dec, + struct Curl_easy *data, + struct bufq *inraw) +{ + const unsigned char *inbuf; + size_t inlen; + + while(Curl_bufq_peek(inraw, &inbuf, &inlen)) { + if(dec->head_len == 0) { + dec->head[0] = *inbuf; + Curl_bufq_skip(inraw, 1); + + dec->frame_flags = ws_frame_op2flags(dec->head[0]); + if(!dec->frame_flags) { + failf(data, "WS: unknown opcode: %x", dec->head[0]); + ws_dec_reset(dec); + return CURLE_RECV_ERROR; + } + dec->head_len = 1; + /* ws_dec_info(dec, data, "seeing opcode"); */ + continue; + } + else if(dec->head_len == 1) { + dec->head[1] = *inbuf; + Curl_bufq_skip(inraw, 1); + dec->head_len = 2; + + if(dec->head[1] & WSBIT_MASK) { + /* A client MUST close a connection if it detects a masked frame. */ + failf(data, "WS: masked input frame"); + ws_dec_reset(dec); + return CURLE_RECV_ERROR; + } + /* How long is the frame head? */ + if(dec->head[1] == 126) { + dec->head_total = 4; + continue; + } + else if(dec->head[1] == 127) { + dec->head_total = 10; + continue; + } + else { + dec->head_total = 2; + } + } + + if(dec->head_len < dec->head_total) { + dec->head[dec->head_len] = *inbuf; + Curl_bufq_skip(inraw, 1); + ++dec->head_len; + if(dec->head_len < dec->head_total) { + /* ws_dec_info(dec, data, "decoding head"); */ + continue; + } + } + /* got the complete frame head */ + DEBUGASSERT(dec->head_len == dec->head_total); + switch(dec->head_total) { + case 2: + dec->payload_len = dec->head[1]; + break; + case 4: + dec->payload_len = (dec->head[2] << 8) | dec->head[3]; + break; + case 10: + dec->payload_len = ((curl_off_t)dec->head[2] << 56) | + (curl_off_t)dec->head[3] << 48 | + (curl_off_t)dec->head[4] << 40 | + (curl_off_t)dec->head[5] << 32 | + (curl_off_t)dec->head[6] << 24 | + (curl_off_t)dec->head[7] << 16 | + (curl_off_t)dec->head[8] << 8 | + dec->head[9]; + break; + default: + /* this should never happen */ + DEBUGASSERT(0); + failf(data, "WS: unexpected frame header length"); + return CURLE_RECV_ERROR; + } + + dec->frame_age = 0; + dec->payload_offset = 0; + ws_dec_info(dec, data, "decoded"); + return CURLE_OK; + } + return CURLE_AGAIN; +} + +static CURLcode ws_dec_pass_payload(struct ws_decoder *dec, + struct Curl_easy *data, + struct bufq *inraw, + ws_write_payload *write_payload, + void *write_ctx) +{ + const unsigned char *inbuf; + size_t inlen; + ssize_t nwritten; + CURLcode result; + curl_off_t remain = dec->payload_len - dec->payload_offset; + + (void)data; + while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen)) { + if((curl_off_t)inlen > remain) + inlen = (size_t)remain; + nwritten = write_payload(inbuf, inlen, dec->frame_age, dec->frame_flags, + dec->payload_offset, dec->payload_len, + write_ctx, &result); + if(nwritten < 0) + return result; + Curl_bufq_skip(inraw, (size_t)nwritten); + dec->payload_offset += (curl_off_t)nwritten; + remain = dec->payload_len - dec->payload_offset; + /* infof(data, "WS-DEC: passed %zd bytes payload, %" + CURL_FORMAT_CURL_OFF_T " remain", + nwritten, remain); */ + } + + return remain? CURLE_AGAIN : CURLE_OK; +} + +static CURLcode ws_dec_pass(struct ws_decoder *dec, + struct Curl_easy *data, + struct bufq *inraw, + ws_write_payload *write_payload, + void *write_ctx) +{ + CURLcode result; + + if(Curl_bufq_is_empty(inraw)) + return CURLE_AGAIN; + + switch(dec->state) { + case WS_DEC_INIT: + ws_dec_reset(dec); + dec->state = WS_DEC_HEAD; + /* FALLTHROUGH */ + case WS_DEC_HEAD: + result = ws_dec_read_head(dec, data, inraw); + if(result) { + if(result != CURLE_AGAIN) { + infof(data, "WS: decode error %d", (int)result); + break; /* real error */ + } + /* incomplete ws frame head */ + DEBUGASSERT(Curl_bufq_is_empty(inraw)); + break; + } + /* head parsing done */ + dec->state = WS_DEC_PAYLOAD; + if(dec->payload_len == 0) { + ssize_t nwritten; + const unsigned char tmp = '\0'; + /* special case of a 0 length frame, need to write once */ + nwritten = write_payload(&tmp, 0, dec->frame_age, dec->frame_flags, + 0, 0, write_ctx, &result); + if(nwritten < 0) + return result; + dec->state = WS_DEC_INIT; + break; + } + /* FALLTHROUGH */ + case WS_DEC_PAYLOAD: + result = ws_dec_pass_payload(dec, data, inraw, write_payload, write_ctx); + ws_dec_info(dec, data, "passing"); + if(result) + return result; + /* paylod parsing done */ + dec->state = WS_DEC_INIT; + break; + default: + /* we covered all enums above, but some code analyzers are whimps */ + result = CURLE_FAILED_INIT; + } + return result; +} + +static void update_meta(struct websocket *ws, + int frame_age, int frame_flags, + curl_off_t payload_offset, + curl_off_t payload_len, + size_t cur_len) +{ + ws->frame.age = frame_age; + ws->frame.flags = frame_flags; + ws->frame.offset = payload_offset; + ws->frame.len = cur_len; + ws->frame.bytesleft = (payload_len - payload_offset - cur_len); +} + +static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data, + const char *msg) +{ + infof(data, "WS-ENC: %s [%s%s%s payload=%" CURL_FORMAT_CURL_OFF_T + "/%" CURL_FORMAT_CURL_OFF_T "]", + msg, ws_frame_name_of_op(enc->firstbyte), + (enc->firstbyte & WSBIT_OPCODE_MASK) == WSBIT_OPCODE_CONT ? + " CONT" : "", + (enc->firstbyte & WSBIT_FIN)? "" : " NON-FIN", + enc->payload_len - enc->payload_remain, enc->payload_len); +} + +static void ws_enc_reset(struct ws_encoder *enc) +{ + enc->payload_remain = 0; + enc->xori = 0; + enc->contfragment = FALSE; +} + +static void ws_enc_init(struct ws_encoder *enc) +{ + ws_enc_reset(enc); +} + +/*** + 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 ... | + +---------------------------------------------------------------+ +*/ + +static ssize_t ws_enc_write_head(struct Curl_easy *data, + struct ws_encoder *enc, + unsigned int flags, + curl_off_t payload_len, + struct bufq *out, + CURLcode *err) +{ + unsigned char firstbyte = 0; + unsigned char opcode; + unsigned char head[14]; + size_t hlen; + ssize_t n; + + if(enc->payload_remain > 0) { + /* trying to write a new frame before the previous one is finished */ + failf(data, "WS: starting new frame with %zd bytes from last one" + "remaining to be sent", (ssize_t)enc->payload_remain); + *err = CURLE_SEND_ERROR; + return -1; + } + + opcode = ws_frame_flags2op(flags); + if(!opcode) { + failf(data, "WS: provided flags not recognized '%x'", flags); + *err = CURLE_SEND_ERROR; + return -1; + } + + if(!(flags & CURLWS_CONT)) { + if(!enc->contfragment) + /* not marked as continuing, this is the final fragment */ + firstbyte |= WSBIT_FIN | opcode; + else + /* marked as continuing, this is the final fragment; set CONT + opcode and FIN bit */ + firstbyte |= WSBIT_FIN | WSBIT_OPCODE_CONT; + + enc->contfragment = FALSE; + } + else if(enc->contfragment) { + /* the previous fragment was not a final one and this isn't either, keep a + CONT opcode and no FIN bit */ + firstbyte |= WSBIT_OPCODE_CONT; + } + else { + firstbyte = opcode; + enc->contfragment = TRUE; + } + + head[0] = enc->firstbyte = firstbyte; + if(payload_len > 65535) { + head[1] = 127 | WSBIT_MASK; + head[2] = (unsigned char)((payload_len >> 56) & 0xff); + head[3] = (unsigned char)((payload_len >> 48) & 0xff); + head[4] = (unsigned char)((payload_len >> 40) & 0xff); + head[5] = (unsigned char)((payload_len >> 32) & 0xff); + head[6] = (unsigned char)((payload_len >> 24) & 0xff); + head[7] = (unsigned char)((payload_len >> 16) & 0xff); + head[8] = (unsigned char)((payload_len >> 8) & 0xff); + head[9] = (unsigned char)(payload_len & 0xff); + hlen = 10; + } + else if(payload_len >= 126) { + head[1] = 126 | WSBIT_MASK; + head[2] = (unsigned char)((payload_len >> 8) & 0xff); + head[3] = (unsigned char)(payload_len & 0xff); + hlen = 4; + } + else { + head[1] = (unsigned char)payload_len | WSBIT_MASK; + hlen = 2; + } + + enc->payload_remain = enc->payload_len = payload_len; + ws_enc_info(enc, data, "sending"); + + /* add 4 bytes mask */ + memcpy(&head[hlen], &enc->mask, 4); + hlen += 4; + /* reset for payload to come */ + enc->xori = 0; + + n = Curl_bufq_write(out, head, hlen, err); + if(n < 0) + return -1; + if((size_t)n != hlen) { + /* We use a bufq with SOFT_LIMIT, writing should always succeed */ + DEBUGASSERT(0); + *err = CURLE_SEND_ERROR; + return -1; + } + return n; +} + +static ssize_t ws_enc_write_payload(struct ws_encoder *enc, + struct Curl_easy *data, + const unsigned char *buf, size_t buflen, + struct bufq *out, CURLcode *err) +{ + ssize_t n; + size_t i, len; + + if(Curl_bufq_is_full(out)) { + *err = CURLE_AGAIN; + return -1; + } + + /* not the most performant way to do this */ + len = buflen; + if((curl_off_t)len > enc->payload_remain) + len = (size_t)enc->payload_remain; + + for(i = 0; i < len; ++i) { + unsigned char c = buf[i] ^ enc->mask[enc->xori]; + n = Curl_bufq_write(out, &c, 1, err); + if(n < 0) { + if((*err != CURLE_AGAIN) || !i) + return -1; + break; + } + enc->xori++; + enc->xori &= 3; + } + enc->payload_remain -= (curl_off_t)i; + ws_enc_info(enc, data, "buffered"); + return (ssize_t)i; +} + + +struct wsfield { + const char *name; + const char *val; +}; + +CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req) +{ + unsigned int i; + CURLcode result = CURLE_OK; + unsigned char rand[16]; + char *randstr; + size_t randlen; + char keyval[40]; + struct SingleRequest *k = &data->req; + struct wsfield heads[]= { + { + /* The request MUST contain an |Upgrade| header field whose value + MUST include the "websocket" keyword. */ + "Upgrade:", "websocket" + }, + { + /* The request MUST contain a |Connection| header field whose value + MUST include the "Upgrade" token. */ + "Connection:", "Upgrade", + }, + { + /* The request MUST include a header field with the name + |Sec-WebSocket-Version|. The value of this header field MUST be + 13. */ + "Sec-WebSocket-Version:", "13", + }, + { + /* The request MUST include a header field with the name + |Sec-WebSocket-Key|. The value of this header field MUST be a nonce + consisting of a randomly selected 16-byte value that has been + base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be + selected randomly for each connection. */ + "Sec-WebSocket-Key:", NULL, + } + }; + heads[3].val = &keyval[0]; + + /* 16 bytes random */ + result = Curl_rand(data, (unsigned char *)rand, sizeof(rand)); + if(result) + return result; + result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen); + if(result) + return result; + DEBUGASSERT(randlen < sizeof(keyval)); + if(randlen >= sizeof(keyval)) + return CURLE_FAILED_INIT; + strcpy(keyval, randstr); + free(randstr); + for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) { + if(!Curl_checkheaders(data, STRCONST(heads[i].name))) { +#ifdef USE_HYPER + char field[128]; + msnprintf(field, sizeof(field), "%s %s", heads[i].name, + heads[i].val); + result = Curl_hyper_header(data, req, field); +#else + (void)data; + result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name, + heads[i].val); +#endif + } + } + k->upgr101 = UPGR101_WS; + return result; +} + +/* + * 'nread' is number of bytes of websocket data already in the buffer at + * 'mem'. + */ +CURLcode Curl_ws_accept(struct Curl_easy *data, + const char *mem, size_t nread) +{ + struct SingleRequest *k = &data->req; + struct websocket *ws; + CURLcode result; + + DEBUGASSERT(data->conn); + ws = data->conn->proto.ws; + if(!ws) { + ws = calloc(1, sizeof(*ws)); + if(!ws) + return CURLE_OUT_OF_MEMORY; + data->conn->proto.ws = ws; + Curl_bufq_init(&ws->recvbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT); + Curl_bufq_init2(&ws->sendbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT, + BUFQ_OPT_SOFT_LIMIT); + ws_dec_init(&ws->dec); + ws_enc_init(&ws->enc); + } + else { + Curl_bufq_reset(&ws->recvbuf); + ws_dec_reset(&ws->dec); + ws_enc_reset(&ws->enc); + } + /* Verify the Sec-WebSocket-Accept response. + + 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". + */ + + /* 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 + not present in the client's handshake (the server has indicated a + subprotocol not requested by the client), the client MUST Fail + the WebSocket Connection. */ + + /* 4 bytes random */ + + result = Curl_rand(data, (unsigned char *)&ws->enc.mask, + sizeof(ws->enc.mask)); + if(result) + return result; + infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x", + ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]); + + if(data->set.connect_only) { + ssize_t nwritten; + /* In CONNECT_ONLY setup, the payloads from `mem` need to be received + * when using `curl_ws_recv` later on after this transfer is already + * marked as DONE. */ + nwritten = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)mem, + nread, &result); + if(nwritten < 0) + return result; + infof(data, "%zu bytes websocket payload", nread); + } + k->upgr101 = UPGR101_RECEIVED; + + return result; +} + +static ssize_t ws_client_write(const unsigned char *buf, size_t buflen, + int frame_age, int frame_flags, + curl_off_t payload_offset, + curl_off_t payload_len, + void *userp, + CURLcode *err) +{ + struct Curl_easy *data = userp; + struct websocket *ws; + size_t wrote; + curl_off_t remain = (payload_len - (payload_offset + buflen)); + + (void)frame_age; + if(!data->conn || !data->conn->proto.ws) { + *err = CURLE_FAILED_INIT; + return -1; + } + ws = data->conn->proto.ws; + + if((frame_flags & CURLWS_PING) && !remain) { + /* auto-respond to PINGs, only works for single-frame payloads atm */ + size_t bytes; + infof(data, "WS: auto-respond to PING with a PONG"); + /* send back the exact same content as a PONG */ + *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG); + if(*err) + return -1; + } + else if(buflen || !remain) { + /* deliver the decoded frame to the user callback. The application + * may invoke curl_ws_meta() to access frame information. */ + update_meta(ws, frame_age, frame_flags, payload_offset, + payload_len, buflen); + Curl_set_in_callback(data, true); + wrote = data->set.fwrite_func((char *)buf, 1, + buflen, data->set.out); + Curl_set_in_callback(data, false); + if(wrote != buflen) { + *err = CURLE_RECV_ERROR; + return -1; + } + } + *err = CURLE_OK; + return (ssize_t)buflen; +} + +/* Curl_ws_writecb() is the write callback for websocket traffic. The + websocket data is provided to this raw, in chunks. This function should + handle/decode the data and call the "real" underlying callback accordingly. +*/ +size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */, + size_t nitems, void *userp) +{ + struct Curl_easy *data = userp; + + if(data->set.ws_raw_mode) + return data->set.fwrite_func(buffer, size, nitems, data->set.out); + else if(nitems) { + struct websocket *ws; + CURLcode result; + + if(!data->conn || !data->conn->proto.ws) { + failf(data, "WS: not a websocket transfer"); + return nitems - 1; + } + ws = data->conn->proto.ws; + + if(buffer) { + ssize_t nwritten; + + nwritten = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)buffer, + nitems, &result); + if(nwritten < 0) { + infof(data, "WS: error adding data to buffer %d", (int)result); + return nitems - 1; + } + buffer = NULL; + } + + while(!Curl_bufq_is_empty(&ws->recvbuf)) { + + result = ws_dec_pass(&ws->dec, data, &ws->recvbuf, + ws_client_write, data); + if(result == CURLE_AGAIN) + /* insufficient amount of data, keep it for later. + * we pretend to have written all since we have a copy */ + return nitems; + else if(result) { + infof(data, "WS: decode error %d", (int)result); + return nitems - 1; + } + } + } + return nitems; +} + +struct ws_collect { + struct Curl_easy *data; + void *buffer; + size_t buflen; + size_t bufidx; + int frame_age; + int frame_flags; + curl_off_t payload_offset; + curl_off_t payload_len; + bool written; +}; + +static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen, + int frame_age, int frame_flags, + curl_off_t payload_offset, + curl_off_t payload_len, + void *userp, + CURLcode *err) +{ + struct ws_collect *ctx = userp; + size_t nwritten; + curl_off_t remain = (payload_len - (payload_offset + buflen)); + + if(!ctx->bufidx) { + /* first write */ + ctx->frame_age = frame_age; + ctx->frame_flags = frame_flags; + ctx->payload_offset = payload_offset; + ctx->payload_len = payload_len; + } + + if((frame_flags & CURLWS_PING) && !remain) { + /* auto-respond to PINGs, only works for single-frame payloads atm */ + size_t bytes; + infof(ctx->data, "WS: auto-respond to PING with a PONG"); + /* send back the exact same content as a PONG */ + *err = curl_ws_send(ctx->data, buf, buflen, &bytes, 0, CURLWS_PONG); + if(*err) + return -1; + nwritten = bytes; + } + else { + ctx->written = TRUE; + DEBUGASSERT(ctx->buflen >= ctx->bufidx); + nwritten = CURLMIN(buflen, ctx->buflen - ctx->bufidx); + if(!nwritten) { + if(!buflen) { /* 0 length write, we accept that */ + *err = CURLE_OK; + return 0; + } + *err = CURLE_AGAIN; /* no more space */ + return -1; + } + *err = CURLE_OK; + memcpy(ctx->buffer, buf, nwritten); + ctx->bufidx += nwritten; + } + return nwritten; +} + +static ssize_t nw_in_recv(void *reader_ctx, + unsigned char *buf, size_t buflen, + CURLcode *err) +{ + struct Curl_easy *data = reader_ctx; + size_t nread; + + *err = curl_easy_recv(data, buf, buflen, &nread); + if(*err) + return -1; + return (ssize_t)nread; +} + +CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, + size_t buflen, size_t *nread, + const struct curl_ws_frame **metap) +{ + struct connectdata *conn = data->conn; + struct websocket *ws; + bool done = FALSE; /* not filled passed buffer yet */ + struct ws_collect ctx; + CURLcode result; + + if(!conn) { + /* Unhappy hack with lifetimes of transfers and connection */ + if(!data->set.connect_only) { + failf(data, "CONNECT_ONLY is required"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + + Curl_getconnectinfo(data, &conn); + if(!conn) { + failf(data, "connection not found"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } + ws = conn->proto.ws; + if(!ws) { + failf(data, "connection is not setup for websocket"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + *nread = 0; + *metap = NULL; + /* get a download buffer */ + result = Curl_preconnect(data); + if(result) + return result; + + memset(&ctx, 0, sizeof(ctx)); + ctx.data = data; + ctx.buffer = buffer; + ctx.buflen = buflen; + + while(!done) { + /* receive more when our buffer is empty */ + if(Curl_bufq_is_empty(&ws->recvbuf)) { + ssize_t n = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &result); + if(n < 0) { + return result; + } + else if(n == 0) { + /* connection closed */ + infof(data, "connection expectedly closed?"); + return CURLE_GOT_NOTHING; + } + DEBUGF(infof(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 */ + } + done = TRUE; + break; + } + else if(result) { + return result; + } + 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. */ + done = TRUE; + 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->frame; + *nread = ws->frame.len; + /* infof(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %" + CURL_FORMAT_CURL_OFF_T ", %" CURL_FORMAT_CURL_OFF_T " left)", + buflen, *nread, ws->frame.offset, ws->frame.bytesleft); */ + return CURLE_OK; +} + +static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, + bool complete) +{ + if(!Curl_bufq_is_empty(&ws->sendbuf)) { + CURLcode result; + const unsigned char *out; + size_t outlen; + ssize_t n; + + while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) { + if(data->set.connect_only) + result = Curl_senddata(data, out, outlen, &n); + else + result = Curl_write(data, data->conn->writesockfd, out, outlen, &n); + if(result) { + if(result == CURLE_AGAIN) { + if(!complete) { + infof(data, "WS: flush EAGAIN, %zu bytes remain in buffer", + Curl_bufq_len(&ws->sendbuf)); + return result; + } + /* TODO: the current design does not allow for buffered writes. + * We need to flush the buffer now. There is no ws_flush() later */ + n = 0; + continue; + } + else if(result) { + failf(data, "WS: flush, write error %d", result); + return result; + } + } + else { + infof(data, "WS: flushed %zu bytes", (size_t)n); + Curl_bufq_skip(&ws->sendbuf, (size_t)n); + } + } + } + return CURLE_OK; +} + +CURL_EXTERN CURLcode curl_ws_send(CURL *data, const void *buffer, + size_t buflen, size_t *sent, + curl_off_t fragsize, + unsigned int flags) +{ + struct websocket *ws; + ssize_t nwritten, n; + size_t space; + CURLcode result; + + *sent = 0; + if(!data->conn && data->set.connect_only) { + result = Curl_connect_only_attach(data); + if(result) + return result; + } + if(!data->conn) { + failf(data, "No associated connection"); + return CURLE_SEND_ERROR; + } + if(!data->conn->proto.ws) { + failf(data, "Not a websocket transfer"); + return CURLE_SEND_ERROR; + } + ws = data->conn->proto.ws; + + if(data->set.ws_raw_mode) { + if(fragsize || flags) + return CURLE_BAD_FUNCTION_ARGUMENT; + if(!buflen) + /* nothing to do */ + return CURLE_OK; + /* raw mode sends exactly what was requested, and this is from within + the write callback */ + if(Curl_is_in_callback(data)) { + result = Curl_write(data, data->conn->writesockfd, buffer, buflen, + &nwritten); + } + else + result = Curl_senddata(data, buffer, buflen, &nwritten); + + infof(data, "WS: wanted to send %zu bytes, sent %zu bytes", + buflen, nwritten); + *sent = (nwritten >= 0)? (size_t)nwritten : 0; + return result; + } + + /* Not RAW mode, buf we do the frame encoding */ + result = ws_flush(data, ws, FALSE); + if(result) + return result; + + /* TODO: the current design does not allow partial writes, afaict. + * It is not clear who the application is supposed to react. */ + space = Curl_bufq_space(&ws->sendbuf); + DEBUGF(infof(data, "curl_ws_send(len=%zu), sendbuf len=%zu space %zu", + buflen, Curl_bufq_len(&ws->sendbuf), space)); + if(space < 14) + return CURLE_AGAIN; + + if(flags & CURLWS_OFFSET) { + if(fragsize) { + /* a frame series 'fragsize' bytes big, this is the first */ + n = ws_enc_write_head(data, &ws->enc, flags, fragsize, + &ws->sendbuf, &result); + if(n < 0) + return result; + } + else { + if((curl_off_t)buflen > ws->enc.payload_remain) { + infof(data, "WS: unaligned frame size (sending %zu instead of %" + CURL_FORMAT_CURL_OFF_T ")", + buflen, ws->enc.payload_remain); + } + } + } + else if(!ws->enc.payload_remain) { + n = ws_enc_write_head(data, &ws->enc, flags, (curl_off_t)buflen, + &ws->sendbuf, &result); + if(n < 0) + return result; + } + + n = ws_enc_write_payload(&ws->enc, data, + buffer, buflen, &ws->sendbuf, &result); + if(n < 0) + return result; + + *sent = (size_t)n; + return ws_flush(data, ws, TRUE); +} + +static void ws_free(struct connectdata *conn) +{ + if(conn && conn->proto.ws) { + Curl_bufq_free(&conn->proto.ws->recvbuf); + Curl_bufq_free(&conn->proto.ws->sendbuf); + Curl_safefree(conn->proto.ws); + } +} + +void Curl_ws_done(struct Curl_easy *data) +{ + (void)data; +} + +CURLcode Curl_ws_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + (void)data; + (void)dead_connection; + ws_free(conn); + return CURLE_OK; +} + +CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data) +{ + /* we only return something for websocket, called from within the callback + when not using raw mode */ + if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->conn && + data->conn->proto.ws && !data->set.ws_raw_mode) + return &data->conn->proto.ws->frame; + return NULL; +} + +#else + +CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, + size_t *nread, + const struct curl_ws_frame **metap) +{ + (void)curl; + (void)buffer; + (void)buflen; + (void)nread; + (void)metap; + return CURLE_NOT_BUILT_IN; +} + +CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer, + size_t buflen, size_t *sent, + curl_off_t fragsize, + unsigned int flags) +{ + (void)curl; + (void)buffer; + (void)buflen; + (void)sent; + (void)fragsize; + (void)flags; + return CURLE_NOT_BUILT_IN; +} + +CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data) +{ + (void)data; + return NULL; +} +#endif /* USE_WEBSOCKETS */ diff --git a/Utilities/cmcurl/lib/ws.h b/Utilities/cmcurl/lib/ws.h new file mode 100644 index 0000000..0308a42 --- /dev/null +++ b/Utilities/cmcurl/lib/ws.h @@ -0,0 +1,89 @@ +#ifndef HEADER_CURL_WS_H +#define HEADER_CURL_WS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_WEBSOCKETS + +#ifdef USE_HYPER +#define REQTYPE void +#else +#define REQTYPE struct dynbuf +#endif + +/* a client-side WS frame decoder, parsing frame headers and + * payload, keeping track of current position and stats */ +enum ws_dec_state { + WS_DEC_INIT, + WS_DEC_HEAD, + WS_DEC_PAYLOAD +}; + +struct ws_decoder { + int frame_age; /* zero */ + int frame_flags; /* See the CURLWS_* defines */ + curl_off_t payload_offset; /* the offset parsing is at */ + curl_off_t payload_len; + unsigned char head[10]; + int head_len, head_total; + enum ws_dec_state state; +}; + +/* a client-side WS frame encoder, generating frame headers and + * converting payloads, tracking remaining data in current frame */ +struct ws_encoder { + curl_off_t payload_len; /* payload length of current frame */ + curl_off_t payload_remain; /* remaining payload of current */ + unsigned int xori; /* xor index */ + unsigned char mask[4]; /* 32 bit mask for this connection */ + unsigned char firstbyte; /* first byte of frame we encode */ + bool contfragment; /* set TRUE if the previous fragment sent was not final */ +}; + +/* A websocket connection with en- and decoder that treat frames + * and keep track of boundaries. */ +struct websocket { + struct Curl_easy *data; /* used for write callback handling */ + struct ws_decoder dec; /* decode of we frames */ + struct ws_encoder enc; /* decode of we frames */ + struct bufq recvbuf; /* raw data from the server */ + struct bufq sendbuf; /* raw data to be sent to the server */ + struct curl_ws_frame frame; /* the current WS FRAME received */ +}; + +CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req); +CURLcode Curl_ws_accept(struct Curl_easy *data, const char *mem, size_t len); +size_t Curl_ws_writecb(char *buffer, size_t size, size_t nitems, void *userp); +void Curl_ws_done(struct Curl_easy *data); +CURLcode Curl_ws_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection); +#else +#define Curl_ws_request(x,y) CURLE_OK +#define Curl_ws_done(x) Curl_nop_stmt +#define Curl_ws_free(x) Curl_nop_stmt +#endif + +#endif /* HEADER_CURL_WS_H */ |
